📚 Series: Belajar Golang dari Nol sampai Deploy
← Part 4 Part 5: Goroutine & Channel Part 6: REST API →

Apa itu Concurrency?

Concurrency berarti menjalankan beberapa pekerjaan secara bersamaan. Bayangkan kamu sedang masak: sambil nunggu air mendidih, kamu iris sayur. Itu concurrency!

Di Go, concurrency sangat mudah dan ringan berkat Goroutine. Satu goroutine hanya butuh ~2KB memory, jauh lebih ringan dari thread OS yang butuh ~1MB.

Goroutine

package main

import (
    "fmt"
    "time"
)

func tugasBerat(nama string) {
    for i := 1; i <= 3; i++ {
        fmt.Printf("%s: langkah %d\n", nama, i)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    // Tanpa goroutine — berurutan
    tugasBerat("Tugas A") // selesai dulu
    tugasBerat("Tugas B") // baru ini jalan

    fmt.Println("---")

    // Dengan goroutine — paralel
    go tugasBerat("Goroutine A")
    go tugasBerat("Goroutine B")
    go tugasBerat("Goroutine C")

    time.Sleep(500 * time.Millisecond) // tunggu goroutine selesai
    fmt.Println("Semua goroutine selesai")
}

⚠️ Kita tidak bisa pakai time.Sleep untuk menunggu goroutine di kode production. Kita perlu cara yang lebih proper — itulah kegunaan WaitGroup dan Channel.

sync.WaitGroup

import (
    "fmt"
    "sync"
)

func prosesTugas(id int, wg *sync.WaitGroup) {
    defer wg.Done() // sinyal bahwa goroutine ini selesai

    fmt.Printf("Memproses tugas #%d\n", id)
    // ... lakukan sesuatu
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1) // tambah counter
        go prosesTugas(i, &wg)
    }

    wg.Wait() // tunggu semua goroutine selesai
    fmt.Println("Semua tugas selesai!")
}

Channel

Channel adalah "pipa" untuk komunikasi antar goroutine. Data masuk di satu ujung, keluar di ujung lain.

func hitungKuadrat(n int, ch chan int) {
    ch <- n * n // kirim hasil ke channel
}

func main() {
    ch := make(chan int)

    go hitungKuadrat(5, ch)
    go hitungKuadrat(9, ch)

    hasil1 := <-ch // terima dari channel
    hasil2 := <-ch
    fmt.Println(hasil1, hasil2) // 25 81 (urutan tidak dijamin)
}

// Buffered channel — tidak blocking sampai buffer penuh
buffered := make(chan string, 3)
buffered <- "a" // tidak blocking
buffered <- "b"
buffered <- "c"
// buffered <- "d" // ini akan blocking karena buffer penuh

fmt.Println(<-buffered) // "a"
fmt.Println(<-buffered) // "b"

Select Statement

// Select — tunggu dari beberapa channel sekaligus
ch1 := make(chan string)
ch2 := make(chan string)

go func() {
    time.Sleep(100 * time.Millisecond)
    ch1 <- "dari channel 1"
}()

go func() {
    time.Sleep(200 * time.Millisecond)
    ch2 <- "dari channel 2"
}()

for i := 0; i < 2; i++ {
    select {
    case msg1 := <-ch1:
        fmt.Println(msg1)
    case msg2 := <-ch2:
        fmt.Println(msg2)
    case <-time.After(300 * time.Millisecond):
        fmt.Println("timeout!")
    }
}

Mutex — Cegah Race Condition

import "sync"

type Counter struct {
    mu    sync.Mutex
    value int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.value++
}

func (c *Counter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

func main() {
    counter := &Counter{}
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Increment()
        }()
    }

    wg.Wait()
    fmt.Println("Counter:", counter.Value()) // selalu 1000
}

Worker Pool Pattern

Pattern umum untuk memproses banyak pekerjaan dengan jumlah worker yang terbatas:

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        fmt.Printf("Worker %d memproses job %d\n", id, job)
        results <- job * 2 // kirim hasil
    }
}

func main() {
    jobs    := make(chan int, 10)
    results := make(chan int, 10)
    var wg sync.WaitGroup

    // Buat 3 worker
    for w := 1; w <= 3; w++ {
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }

    // Kirim 9 pekerjaan
    for j := 1; j <= 9; j++ {
        jobs <- j
    }
    close(jobs) // sinyal tidak ada job lagi

    // Tunggu semua worker, lalu tutup results
    go func() {
        wg.Wait()
        close(results)
    }()

    // Kumpulkan hasil
    for r := range results {
        fmt.Println("Hasil:", r)
    }
}

Checklist Progress

← Part 4 Part 6: REST API →