🖥️ VS Code: Visualisasi Goroutine saat Debug

Saat debugging Go di VS Code (tekan F5), panel Goroutines di sidebar kiri menampilkan semua goroutine yang sedang berjalan beserta status-nya. Sangat berguna untuk mendeteksi goroutine yang deadlock. Pastikan extension Go by Google sudah terinstall.

Concurrency adalah kemampuan menjalankan banyak tugas secara bersamaan. Go memiliki fitur concurrency built-in yang sangat ringan — kamu bisa menjalankan ribuan goroutine sekaligus tanpa masalah memori yang serius.

Apa itu Goroutine?

Goroutine adalah fungsi yang berjalan secara konkuren (bersamaan) dengan goroutine lain. Berbeda dengan thread OS yang berat, goroutine sangat ringan — hanya sekitar 2KB memori saat pertama dibuat, dan Go runtime yang mengatur penjadwalannya.

package main

import (
    "fmt"
    "time"
)

func tugasA() {
    for i := 0; i < 3; i++ {
        fmt.Println("Tugas A:", i)
        time.Sleep(100 * time.Millisecond)
    }
}

func tugasB() {
    for i := 0; i < 3; i++ {
        fmt.Println("Tugas B:", i)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    go tugasA()   // jalankan sebagai goroutine
    go tugasB()   // jalankan sebagai goroutine
    time.Sleep(1 * time.Second)  // tunggu goroutine selesai
}
⚠️ Masalah dengan time.Sleep: Menggunakan time.Sleep untuk menunggu goroutine adalah cara yang tidak tepat. Pakai sync.WaitGroup atau channel.

sync.WaitGroup

WaitGroup dipakai untuk menunggu sekumpulan goroutine selesai:

package main

import (
    "fmt"
    "sync"
)

func proses(id int, wg *sync.WaitGroup) {
    defer wg.Done()   // kurangi counter saat selesai
    fmt.Printf("Goroutine %d selesai\n", id)
}

func main() {
    var wg sync.WaitGroup

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

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

Channel

Channel adalah "pipa" untuk mengirim data antar goroutine dengan aman. Ini cara idiomatik Go untuk komunikasi antar goroutine.

// Buat channel
ch := make(chan int)

// Kirim data ke channel (dalam goroutine)
go func() {
    ch <- 42   // kirim nilai 42
}()

// Terima data dari channel
nilai := <-ch
fmt.Println(nilai)  // 42

Contoh nyata — hitung di goroutine, kirim hasilnya:

func hitungKuadrat(angka int, ch chan int) {
    ch <- angka * angka
}

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

    go hitungKuadrat(4, ch)
    go hitungKuadrat(5, ch)

    a, b := <-ch, <-ch
    fmt.Println(a, b)  // 16 25 (urutan bisa berbeda)
}

Buffered Channel

Channel biasa bersifat blocking — pengirim menunggu penerima siap. Buffered channel punya antrian internal:

// Buffered channel dengan kapasitas 3
ch := make(chan string, 3)

ch <- "pesan 1"   // tidak blocking
ch <- "pesan 2"   // tidak blocking
ch <- "pesan 3"   // tidak blocking
// ch <- "pesan 4"  // ini akan blocking karena penuh

fmt.Println(<-ch)  // "pesan 1"
fmt.Println(<-ch)  // "pesan 2"

Select

select memungkinkan goroutine menunggu beberapa channel sekaligus, mengeksekusi case yang pertama kali siap:

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() { ch1 <- "dari ch1" }()
    go func() { ch2 <- "dari ch2" }()

    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println(msg1)
        case msg2 := <-ch2:
            fmt.Println(msg2)
        }
    }
}

Done Pattern (Cancel Goroutine)

Cara umum untuk menghentikan goroutine yang berjalan lama:

func worker(done <-chan bool) {
    for {
        select {
        case <-done:
            fmt.Println("Worker berhenti")
            return
        default:
            fmt.Println("Worker bekerja...")
        }
    }
}

func main() {
    done := make(chan bool)
    go worker(done)

    time.Sleep(3 * time.Second)
    done <- true  // kirim sinyal berhenti
}

Mutex: Proteksi Data Bersama

Ketika beberapa goroutine mengakses variabel yang sama, gunakan mutex untuk mencegah race condition:

package main

import (
    "fmt"
    "sync"
)

type Counter struct {
    mu    sync.Mutex
    nilai int
}

func (c *Counter) Tambah() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.nilai++
}

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

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

    wg.Wait()
    fmt.Println("Nilai:", counter.nilai)  // pasti 1000
}
💡 Prinsip Concurrency Go

"Do not communicate by sharing memory; instead, share memory by communicating."

Artinya: daripada beberapa goroutine mengakses variabel yang sama (butuh mutex), lebih baik kirim data melalui channel. Channel adalah cara idiomatik dan lebih aman.