Goroutine adalah fungsi yang jalan bersamaan dengan fungsi lain. Cara memulainya cukup menulis go di depan pemanggilan fungsi. Bayangin kasir Warung Bakso Mas Bimo. Tiap pesanan diserahkan ke juru masak di dapur, lalu kasir langsung melayani pembeli berikutnya tanpa menunggu masakan matang. Kasirnya main, tiap juru masak satu goroutine. Tapi ada satu aturan keras: begitu kasir pulang, warung tutup, dan semua juru masak ikut pulang, masakannya sudah matang atau belum.
🧪 Tebakan cepat:
mainmemulai tiga goroutinemasak, mencetakkasir: 3 pesanan masuk, lalu selesai. Berapa baris yang keluar di terminal? Tebak dulu, jawabannya ada di bagian "main gak menunggu".
Memulai goroutine dengan go
package main
import "fmt"
func masak(meja int, menu string) {
fmt.Println("dapur: meja", meja, menu, "siap")
}
func main() {
go masak(1, "bakso urat")
go masak(2, "mie ayam")
go masak(3, "es teh")
fmt.Println("kasir: 3 pesanan masuk")
}
go masak(1, "bakso urat")memulaimasakdi goroutine baru, lalumainlangsung lanjut ke baris berikutnya tanpa menunggu.- Fungsi yang sama tetap bisa dipanggil biasa,
masak(1, "bakso urat"), dan saat itumainmenunggunya selesai. mainsendiri juga goroutine, yang pertama. Program Go berhenti begitumainselesai.
main gak menunggu
$ go run bakso.go
kasir: 3 pesanan masuk
Cuma satu baris. main mencetak baris kasir lalu selesai, dan begitu main selesai, program berhenti beserta semua goroutine-nya, sebelum satu pun sempat mencetak. Itu jawaban tebakannya. Di 8 kali percobaan di Go Playground hasilnya selalu satu baris ini, tapi di mesin lain kadang satu baris dapur sempat lolos. Gak ada yang dijamin.
time.Sleep cuma menebak
Menambah time.Sleep(100 * time.Millisecond) sebelum main selesai membuat ketiga baris dapur muncul, karena masaknya cepat. Tapi kalau tiap masakan butuh 200 ms:
$ go run bakso.go
kasir: 3 pesanan masuk
kasir: semua pesanan siap
Kasir bilang semua siap padahal belum ada yang matang. Sleep cuma menebak berapa lama harus menunggu, dan tebakannya bisa salah kapan saja.
sync.WaitGroup: tunggu sampai semua selesai
sync.WaitGroup adalah penghitung kerjaan yang belum selesai:
package main
import (
"fmt"
"sync"
)
func masak(meja int, menu string, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("dapur: meja", meja, menu, "siap")
}
func main() {
var wg sync.WaitGroup
wg.Add(3)
go masak(1, "bakso urat", &wg)
go masak(2, "mie ayam", &wg)
go masak(3, "es teh", &wg)
fmt.Println("kasir: 3 pesanan masuk")
wg.Wait()
fmt.Println("kasir: semua pesanan siap")
}
$ go run bakso.go
kasir: 3 pesanan masuk
dapur: meja 3 es teh siap
dapur: meja 1 bakso urat siap
dapur: meja 2 mie ayam siap
kasir: semua pesanan siap
wg.Add(3)menambah hitungan jadi 3, sebelum goroutine-nya dimulai.defer wg.Done()mengurangi satu saatmasakselesai, apa pun yang terjadi di dalamnya.wg.Wait()memblokirmainsampai hitungannya 0. Sesudah itu barukasir: semua pesanan siaptercetak, selalu paling akhir.- WaitGroup dikirim sebagai pointer,
&wg. Kalau dikirim sebagai nilai, tiap goroutine memegang salinan,Done()gak pernah mengurangi hitungan milikmain, dan program berakhir denganfatal error: all goroutines are asleep - deadlock!.
Urutannya gak tetap
Tiga baris dapur di atas keluar dengan urutan 3, 1, 2. Jalankan lagi, urutannya bisa lain. Dari 20 kali percobaan di Go Playground (8 CPU), urutan 3, 1, 2 paling sering muncul, tapi 1, 2, 3, lalu 2, 3, 1, 1, 3, 2, 3, 2, 1, dan 2, 1, 3 juga keluar. Goroutine dijadwalkan oleh runtime Go, dan urutan jalannya gak dijamin sama dengan urutan dibuat. Kalau urutan penting, sinkronkan secara eksplisit.
Goroutine berjalan concurrent: banyak kerjaan berjalan saling selang-seling. Di prosesor dengan banyak core, mereka juga bisa benar-benar jalan bersamaan (parallel).
Kenapa goroutine murah
Goroutine dimulai dengan stack (memori untuk pemanggilan fungsi) sekitar 2 KB yang membesar sendiri kalau perlu. Thread sistem operasi biasanya dipesan dengan stack berukuran MB. Goroutine juga dijadwalkan oleh runtime Go, bukan oleh sistem operasi: runtime membagi banyak goroutine ke sedikit thread (disebut penjadwalan M:N). Karena itu satu program Go bisa menjalankan ribuan, bahkan ratusan ribu, goroutine sekaligus.
Sejak Go 1.22, variabel loop dibuat baru di tiap putaran, jadi for i, menu := range pesanan { go func() { ... menu ... }() } aman: tiap goroutine memegang menu miliknya. Sejak Go 1.25 ada juga wg.Go(func() { ... }), yang sekaligus melakukan Add(1) dan Done().
⚠️ Jebakan yang sering ketemu
- main selesai duluan. Goroutine yang belum selesai ikut berhenti tanpa pesan apa pun. Tunggu dengan WaitGroup (atau channel, di lesson berikutnya).
- Sleep sebagai penunggu. Kadang cukup, kadang gak. Jangan mengandalkannya.
wg.Adddi dalam goroutine.wg.Wait()bisa jalan sebelumAdd-nya sempat dipanggil. PanggilAddsebelumgo.- Lupa
wg.Done()atau mengirimwgtanpa&. Hitungannya gak pernah 0, dan program mati denganfatal error: all goroutines are asleep - deadlock!.
TL;DR: go f() menjalankan f di goroutine baru, dan pemanggilnya langsung lanjut. Program berhenti begitu main selesai, beserta semua goroutine yang belum beres, jadi tanpa menunggu kamu bisa cuma melihat kasir: 3 pesanan masuk. time.Sleep cuma menebak. Pakai sync.WaitGroup: wg.Add(n) sebelum go, defer wg.Done() di dalam goroutine, wg.Wait() di main, dan kirim &wg. Urutan goroutine gak tetap. Goroutine murah: stack awal sekitar 2 KB dan dijadwalkan runtime Go, bukan sistem operasi.