Goroutine and its patterns (Worker Pool)

Apa Itu Goroutine?

Kalau lu baru belajar Go, pasti sering banget denger kata “goroutine”. Jadi, goroutine itu kayak “lightweight thread” di Go. Simpelnya, goroutine bikin lu bisa jalanin beberapa pekerjaan sekaligus dalam satu aplikasi tanpa pusing-pusing mikirin thread.

Bayangin lu lagi masak dan cuci piring barengan. Masak itu satu pekerjaan, cuci piring itu pekerjaan lain. Nah, goroutine bikin hal kayak gitu jadi mudah banget di dunia programming.

Cara bikin goroutine juga gampang banget. Tinggal pake keyword go sebelum fungsi yang mau lu jalanin.


package main

  

import (

  "fmt"

  "time"

)

  

func printMessage(message string) {

  for i := 0; i < 5; i++ {

    fmt.Println(message)

    time.Sleep(500 * time.Millisecond)

  }

}

  

func main() {

  go printMessage("Halo dari Goroutine!")

  printMessage("Halo dari Main Thread!")

}

Kalau lu jalanin, output-nya bakal kelihatan tumpang tindih atau overlapping karena kedua fungsi jalan barengan. Itu adalah contoh sederhana gimana goroutine bikin kode lu lebih “async”. Nah proses overlapping ini yang ngebedain antara concurrency dan parallelism.


Concurrency vs Parallelism

Ini dua konsep yang sering banget bikin bingung. Jadi, gua bakal jelasin pake analogi biar gampang.

Concurrency

Bayangin lu lagi di kedai kopi, nunggu pesenan lu. Sambil nunggu, lu baca buku. Nah, di sini lu ngerjain dua hal: nunggu kopi dan baca buku. Tapi, sebenarnya lu nggak bener-bener ngerjain keduanya dalam waktu yang sama. Lu baca buku, berhenti sebentar ngecek kopi, terus balik lagi baca buku. Ini yang namanya concurrency: lu “switch” antar pekerjaan biar semuanya kelar.

Parallelism

Sekarang bayangin lu punya temen. Lu suruh dia nunggu kopi, sementara lu fokus baca buku. Di sini, lu dan temen lu ngerjain dua hal bener-bener bersamaan. Nah, ini yang namanya parallelism.

Bedanya Apa?

  • Concurrency lebih fokus ke ngatur banyak tugas supaya kelar barengan (walaupun nggak benar-benar barengan).

  • Parallelism itu bener-bener jalanin banyak tugas secara bersamaan.

Goroutine sebenarnya lebih fokus ke concurrency, tapi kalau lu punya banyak CPU core, goroutine juga bisa jalanin parallelism.


Worker Pool Pattern

Kadang kalau lu jalanin terlalu banyak goroutine sekaligus, malah bisa bikin aplikasi lu berat atau crash. Misalnya, lu bikin goroutine buat ngolah data, terus datanya ada 10.000 item. Kalau lu bikin 10.000 goroutine langsung, CPU bisa kewalahan. Solusinya? Worker Pool.

Worker pool itu pattern di mana lu punya beberapa goroutine (“workers”) yang kerja secara bergantian buat handle task. Jadi lebih efisien dan nggak makan resource terlalu banyak.

Contoh Sederhana

Gua kasih contoh bikin worker pool buat ngeproses angka.


package main

  

import (

  "fmt"

  "sync"

)

  

func worker(id int, tasks <-chan int, wg *sync.WaitGroup) {

  for task := range tasks {

    fmt.Printf("Worker %d processing task %d\n", id, task)

  }

  wg.Done()

}

  

func main() {

  tasks := make(chan int, 10)

  var wg sync.WaitGroup

  

  // Bikin 3 worker

  for i := 1; i <= 3; i++ {

    wg.Add(1)

    go worker(i, tasks, &wg)

  }

  

  // Kirim 10 task ke channel

  for j := 1; j <= 10; j++ {

    tasks <- j

  }

  close(tasks)

  

  wg.Wait()

}

Output-nya bakal kelihatan kalau worker 1, 2, dan 3 berbagi tugas ngeproses task dari 1 sampai 10. Jadi nggak semuanya dikerjain barengan, tapi tetep efisien.

Real-World Example

Misalnya lu bikin web scraper buat ngumpulin data dari banyak website. Kalau tiap URL lu scrape pake satu goroutine tanpa worker pool, bisa berat banget. Dengan worker pool, lu batasi misalnya 5 goroutine sekaligus, jadi lebih aman.


package main

  

import (

  "fmt"

  "net/http"

  "sync"

)

  

func fetchURL(id int, urls <-chan string, wg *sync.WaitGroup) {

  for url := range urls {

    resp, err := http.Get(url)

    if err != nil {

      fmt.Printf("Worker %d: Error fetching %s: %s\n", id, url, err)

      continue

    }

    fmt.Printf("Worker %d: Fetched %s with status %d\n", id, url, resp.StatusCode)

    resp.Body.Close()

  }

  wg.Done()

}

  

func main() {

  urls := []string{

    "https://example.com",

    "https://golang.org",

    "https://github.com",

    "https://google.com",

    "https://stackoverflow.com",

  }

  

  urlChan := make(chan string, len(urls))

  var wg sync.WaitGroup

  

  // Bikin 3 worker

  for i := 1; i <= 3; i++ {

    wg.Add(1)

    go fetchURL(i, urlChan, &wg)

  }

  

  // Kirim URL ke channel

  for _, url := range urls {

    urlChan <- url

  }

  close(urlChan)

  

  wg.Wait()

}


Kesimpulan

Goroutine itu keren banget buat bikin aplikasi yang cepat dan responsif. Tapi, lu juga harus ngerti batasannya, terutama soal resource. Dengan pattern kayak worker pool, lu bisa optimalin penggunaan goroutine biar aplikasi lu tetep efisien. Jadi, sekarang lu udah ngerti dasar goroutine, bedanya concurrency dan parallelism, serta gimana implementasi worker pool. Cobain di project lu bre!