Lompat ke konten Lompat ke sidebar Lompat ke footer

Golang Concurrency #5

 Mari kita coba jelaskan mekanismenya secara lebih rinci, dengan ilustrasi perbedaan penempatan wg.Wait() dan penutupan channel di dalam goroutine terpisah dibandingkan jika diletakkan di goroutine utama.


1. Mekanisme Dasar wg.Wait() dan Penutupan Channel

  • wg.Wait():
    Ini adalah panggilan yang blokir (blocking) eksekusi hingga counter pada WaitGroup mencapai 0. Artinya, eksekusi akan berhenti sampai semua goroutine yang telah menambahkan counter (dengan wg.Add()) telah memanggil wg.Done().

  • close(channel):
    Menutup channel menandakan bahwa tidak ada lagi data yang akan dikirim ke channel tersebut. Saat channel ditutup, loop pembacaan (misalnya for result := range channel) akan berhenti.


2. Pola Umum: Menutup Channel dalam Goroutine Terpisah

Contoh Pola:

go func() { wg.Wait() // Tunggu sampai semua goroutine selesai close(results) // Tutup channel setelah semua data dikirim }()

Mengapa dilakukan seperti ini?

  • Non-blocking untuk Goroutine Utama:
    Dengan menempatkan wg.Wait() dan penutupan channel di goroutine terpisah, goroutine utama bisa langsung melanjutkan untuk membaca data dari channel secara konkurens (concurrent) saat worker goroutine mengirimkan hasilnya.

  • Loop Pembacaan yang Lancar:
    Loop for result := range results di goroutine utama akan terus membaca data yang masuk. Ketika semua worker selesai, goroutine terpisah akan memanggil close(results), sehingga loop pembacaan akan berhenti secara otomatis.


3. Jika Ditempatkan di Goroutine Utama

Misalkan kita ubah dan meletakkan wg.Wait() dan penutupan channel langsung di goroutine utama:

// Contoh: Membaca hasil setelah menunggu semua goroutine selesai package main import ( "fmt" "net/http" "sync" "time" ) func fetchURL(url string, wg *sync.WaitGroup, results chan<- string) { defer wg.Done() start := time.Now() resp, err := http.Get(url) elapsed := time.Since(start) if err != nil { results <- fmt.Sprintf("Error fetching %s: %v", url, err) return } defer resp.Body.Close() results <- fmt.Sprintf("Fetched %s in %v (Status: %d)", url, elapsed, resp.StatusCode) } func main() { urls := []string{ "https://golang.org", "https://www.google.com", "https://www.github.com", } var wg sync.WaitGroup results := make(chan string, len(urls)) for _, url := range urls { wg.Add(1) go fetchURL(url, &wg, results) } // Jika kita letakkan wg.Wait() dan close(results) di sini (di main) wg.Wait() // Goroutine utama menunggu semua worker selesai close(results) // Setelah semua selesai, kita tutup channel // Baru setelah itu, kita baca semua hasil for result := range results { fmt.Println(result) } }

Apa yang terjadi pada pola ini?

  • Goroutine Utama Menunggu Semua Selesai:
    Di sini, goroutine utama akan memanggil wg.Wait() dan menunggu sampai semua worker goroutine selesai. Selama periode ini, goroutine utama tidak melakukan apa-apa—ia hanya menunggu.

  • Penggunaan Channel Buffered:
    Karena kita membuat channel dengan kapasitas (buffer) sebesar jumlah URL (len(urls)), worker goroutine dapat mengirimkan hasilnya ke channel tanpa harus menunggu goroutine utama untuk segera membacanya.
    Jika channel tidak buffered, worker bisa terblokir jika goroutine utama tidak membaca, yang dapat menyebabkan deadlock.

  • Pembacaan Hasil Setelah Semua Selesai:
    Setelah wg.Wait() selesai, baru goroutine utama menutup channel dan mulai membaca semua hasil.
    Kelebihannya:

    • Lebih sederhana jika kamu tidak perlu memproses data secara real-time (sambil menunggu).
      Kekurangannya:
    • Jika ada banyak data, mungkin kamu kehilangan kesempatan untuk mulai memproses data segera, sehingga waktu respon keseluruhan bisa terasa lebih lama.

4. Perbandingan Antara Kedua Pola

  1. Menempatkan wg.Wait() dan close(results) di Goroutine Terpisah:

    • Pro:
      • Pemrosesan Data Real-Time: Goroutine utama bisa mulai membaca data dari channel segera setelah data tersedia.
      • Responsif: Hasil bisa langsung diproses tanpa harus menunggu seluruh pekerjaan selesai.
    • Kontra:
      • Sedikit Kompleksitas Tambahan: Ada goroutine ekstra untuk menunggu dan menutup channel, tetapi ini biasanya tidak menjadi masalah.
  2. Menempatkan wg.Wait() dan close(results) di Goroutine Utama:

    • Pro:
      • Implementasi Sederhana: Alur kode lebih linier dan mudah dipahami untuk kasus sederhana.
    • Kontra:
      • Tidak Ada Pemrosesan Paralel Hasil: Goroutine utama tidak akan membaca channel sampai semua worker selesai.
      • Risiko Deadlock Jika Channel Tidak Buffered: Jika channel tidak dibuat dengan buffer yang cukup, worker bisa terblokir saat menulis ke channel karena goroutine utama tidak membaca.

Kesimpulan

  • Menggunakan goroutine terpisah untuk wg.Wait() dan penutupan channel memungkinkan pemrosesan hasil secara konkuren dan lebih responsif, terutama bila hasil perlu segera diproses.
  • Jika diletakkan di goroutine utama, maka eksekusi akan blokir sampai semua pekerjaan selesai, dan pembacaan hasil baru dimulai. Pendekatan ini cocok untuk kasus di mana pemrosesan data setelah selesai semua pekerjaan tidak menjadi masalah.
  • Penting: Pastikan channel memiliki buffer yang cukup jika memilih pola di mana goroutine utama menunggu sebelum membaca, untuk menghindari deadlock.

Semoga penjelasan dan contoh ini membantu memperjelas mekanisme tersebut! Jika masih ada pertanyaan atau perlu contoh tambahan, silakan tanyakan.