Memanggil fungsi secara biasa membuat pemanggil berhenti menunggu fungsi tersebut selesai. Bayangkan program unduhan yang harus menunggu satu berkas selesai sebelum mulai berikutnya — total waktu jumlah dari seluruh penungguan. Go mengganti model itu dengan goroutine: fungsi yang dijalankan secara concurrent oleh runtime sehingga pemanggil bisa langsung melanjutkan pekerjaan lain. Chapter ini mempraktikkan keyword go, menyaksikan apa yang terjadi ketika program keluar tanpa menunggu, lalu memakai sync.WaitGroup untuk menyinkronkan penyelesaian sekumpulan goroutine.
Menjalankan Fungsi dengan keyword go #
Keyword go ditempatkan sebelum pemanggilan fungsi. Runtime kemudian menjalankan fungsi itu sebagai goroutine baru dan sekaligus mengembalikan kendali ke pemanggil, sehingga baris setelah go dieksekusi tanpa menunggu:
go sapa("Budi") // bentuk pemanggilan fungsi bernama
go func() { // bentuk fungsi anonim (anonymous function)
fmt.Println("dari goroutine")
}()Kedua bentuk sama sahnya. Fungsi anonim sering dipakai ketika tugas goroutine cukup pendek dan tidak perlu nama. Parameter dievaluasi pada saat statement go dieksekusi, bukan saat goroutine benar-benar mulai berjalan.
Program Keluar Tanpa Menunggu Goroutine #
Perilaku paling penting yang harus dipahami: fungsi main adalah goroutine juga, dan ketika main kembali, runtime Go menghentikan seluruh program termasuk semua goroutine yang masih berjalan. Berkas goroutine-demo.go membuktikannya:
package main
import (
"fmt"
"time"
)
func sapa(nama string) {
time.Sleep(500 * time.Millisecond) // simulasi kerja yang lama
fmt.Println("Halo,", nama)
}
func main() {
go sapa("Budi")
fmt.Println("program selesai")
}Jalankan dengan perintah berikut dari folder chapter:
go run goroutine-demo.goOutput yang Dihasilkan:
program selesaiMari kita telaah baris demi baris program di atas:
- Baris 3–6 (
import ...): mengimpor packagefmtuntuk mencetak dan packagetimeyang dipakai untuk jeda simulasi kerja. - Baris 8–11 (
func sapa): fungsi yang akan dijalankan sebagai goroutine;time.Sleepdi dalamnya hanya mensimulasikan kerja yang lama — durasinya tidak berperan dalam sinkronisasi. - Baris 14 (
go sapa("Budi")): memulai goroutinesapadan langsung melanjutkan; pemanggilan ini kembali dalam hitungan mikrodetik. - Baris 16 (
fmt.Println(...)):mainmencetak pesan penutup sementara goroutinesapamasih tertidur 500 milidetik. - Baris 17 (akhir
main):mainkembali, runtime menghentikan program, dan goroutinesapaterbunuh sebelum sempat mencetak sapaan — itulah sebabnya output hanya berisi satu baris.
Output satu baris tersebut stabil karena program keluar jauh sebelum tidur 500 milidetik selesai. Tanpa jeda itu, goroutine kadang sempat mencetak dan kadang tidak — urutan eksekusi goroutine tidak boleh dijadikan dasar logika program.
Menunggu Goroutine dengan sync.WaitGroup #
Ketika hasil kerja goroutine dibutuhkan, program harus menunggu secara eksplisit. Alat standarnya adalah sync.WaitGroup: sebuah counter tugas. Method Add(n) menambah n tugas, Done() milik tiap goroutine menandai satu tugas selesai, dan Wait() memblokir pemanggil sampai counter mencapai nol. Berkas goroutine-tunggu-demo.go memakai pola lengkapnya:
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
func main() {
fmt.Println("goroutine di awal :", runtime.NumGoroutine())
hasil := make([]string, 3)
var wg sync.WaitGroup
wg.Add(3)
for i := range 3 {
go func() {
defer wg.Done()
time.Sleep(50 * time.Millisecond) // simulasi kerja
hasil[i] = fmt.Sprintf("tugas %d selesai", i+1)
}()
}
fmt.Println("goroutine saat kerja :", runtime.NumGoroutine())
wg.Wait()
for _, h := range hasil {
fmt.Println(h)
}
}Output yang Dihasilkan:
goroutine di awal : 1
goroutine saat kerja : 4
tugas 1 selesai
tugas 2 selesai
tugas 3 selesaiMari kita telaah baris demi baris program di atas:
- Baris 11 (
runtime.NumGoroutine()): menghitung goroutine yang sedang ada; di awal program hanya adamain, hasilnya1. - Baris 14–15 (
var wg ... wg.Add(3)): counter WaitGroup dinaikkan menjadi 3 sebelum satu pun goroutine dimulai, sesuai posisiAddyang wajib mendahuluigo. - Baris 18 (
go func() {...}()): memulai goroutine anonim; closure menangkap variabel loopiyang sejak Go 1.22 dibuat ulang pada setiap iterasi sehingga tiap goroutine memegang nilaiimiliknya. - Baris 19 (
defer wg.Done()): menjadwalkanDone()agar counter berkurang apa pun jalannya goroutine, sekaligus memastikan pemanggil tidak lupa memanggilnya. - Baris 21 (
hasil[i] = ...): tiap goroutine menulis pada slot indeksnya sendiri — tidak ada dua goroutine menulis lokasi memori yang sama, jadi tidak terjadi data race. - Baris 27 (
wg.Wait()): memblokirmainsampai ketiga goroutine selesai; setelah baris ini aman membaca slicehasilsecara utuh. - Baris 29–31 (
for _, h := range hasil): hasil dicetak terurut menurut urutan slice, bukan urutan goroutine selesai.
Perhatikan bahwa ketiga hasil tercetak terurut: goroutine boleh selesai dalam urutan apa pun, tetapi program mengumpulkan hasil ke slot indeks masing-masing lalu mencetaknya setelah Wait. Setelah wg.Wait() kembali, seluruh pekerjaan tuntas dan slice hasil terisi penuh. Kami sengaja tidak mencetak runtime.NumGoroutine() tepat setelah Wait — goroutine yang baru memanggil Done() boleh masih berada di tengah proses keluarnya, sehingga angka pada titik itu belum pasti. Pembahasan sync.WaitGroup yang lebih dalam menanti di chapter sync.WaitGroup
.
Goroutine Ringan, Jumlahnya Bebas #
Satu goroutine memulai hidup dengan stack kecil — beberapa kilobyte — yang tumbuh otomatis ketika dibutuhkan, jauh lebih murah daripada thread sistem operasi. Penjadwalannya juga terjadi di dalam runtime Go (model M:N pada chapter Concurrency dan Parallelism ), bukan lewat sistem operasi yang mahal. Jumlah goroutine yang wajar karenanya berada di orde ribuan, bukan lusinan; pertumbuhan stack goroutine dibedah di chapter Stack dan Heap .
Referensi #
- Go statements — Spesifikasi Go
— aturan resmi statement
go. - runtime.NumGoroutine — dokumentasi fungsi penghitung goroutine aktif.
- Concurrency — Effective Go — panduan resmi memulai goroutine.
Goroutine sudah berjalan, tetapi program contoh di atas masih memakai slot memori untuk menampung hasil. Go menawarkan alat komunikasi yang lebih idiomatik — channel — yang menjadi fokus chapter berikutnya.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
goroutine-demo.go
Unduh berkaspackage main
import (
"fmt"
"time"
)
func sapa(nama string) {
time.Sleep(500 * time.Millisecond) // simulasi kerja yang lama
fmt.Println("Halo,", nama)
}
func main() {
go sapa("Budi")
fmt.Println("program selesai")
}
goroutine-tunggu-demo.go
Unduh berkaspackage main
import (
"fmt"
"runtime"
"sync"
"time"
)
func main() {
fmt.Println("goroutine di awal :", runtime.NumGoroutine())
hasil := make([]string, 3)
var wg sync.WaitGroup
wg.Add(3)
for i := range 3 {
go func() {
defer wg.Done()
time.Sleep(50 * time.Millisecond) // simulasi kerja
hasil[i] = fmt.Sprintf("tugas %d selesai", i+1)
}()
}
fmt.Println("goroutine saat kerja :", runtime.NumGoroutine())
wg.Wait()
for _, h := range hasil {
fmt.Println(h)
}
}