Semua artikel

Concurrency di Golang: Goroutine, Channel, dan Context dalam Praktik

Cara memakai goroutine tanpa bocor, kapan memilih channel atau mutex, bagaimana context membawa pembatalan sepanjang request, worker pool dengan errgroup, dan kebiasaan memakai race detector agar kode Go yang berjalan paralel tetap benar.

Pengembangan Backend|Dipublikasikan |10 menit baca
Kode di dua monitor terlihat melalui sebuah kacamata

Menjalankan goroutine cukup dengan dua huruf: go. Karena itulah concurrency jadi salah satu hal pertama yang disukai orang dari Go, sekaligus sumber banyak bug produksi yang kami temui di layanan Go. Goroutine yang tidak pernah selesai, map yang ditulis dari dua tempat sekaligus, dan request yang terus berjalan padahal client sudah pergi. Pola-pola di bawah adalah yang kami ajarkan dan pakai sendiri untuk menghindari masalah itu.

Setiap Goroutine Harus Punya Jalan Keluar

Goroutine itu murah, tapi tidak akan dibersihkan selama masih tertahan. Goroutine yang menunggu selamanya di channel yang tidak akan pernah diisi tetap memegang stack dan semua data yang dirujuknya sampai proses di-restart. Sebelum menulis go func(), jawab satu pertanyaan: apa yang membuat goroutine ini selesai? Jawabannya biasanya channel yang ditutup, context yang dibatalkan, atau pekerjaan yang memang ada ujungnya. Kalau tidak ada jawaban, itu kebocoran.

Channel atau Mutex?

SituasiPakaiAlasan
Mengirim pekerjaan atau hasil dari satu goroutine ke goroutine lainChannelKepemilikan data ikut berpindah bersama nilainya, jadi hanya satu goroutine yang memegangnya dalam satu waktu
Memberi sinyal bahwa sesuatu sudah selesai atau harus berhentiChannel yang ditutup atau contextMenutup channel membangunkan semua penerima sekaligus
Beberapa goroutine membaca dan mengubah map atau counter yang samasync.Mutex atau sync.RWMutexLebih sederhana dan lebih cepat daripada mengalirkan setiap perubahan lewat satu goroutine
Satu angka yang diubah dari banyak goroutineTipe sync/atomic seperti atomic.Int64Tidak perlu lock untuk satu nilai

Pepatah Go menyarankan berbagi memori dengan cara berkomunikasi, dan channel memang pilihan awal yang bagus untuk pipeline. Tetap saja, mutex di sekitar struct kecil sering menghasilkan kode yang paling jelas. Pilih yang membuat kepemilikan data paling mudah dipahami pembaca berikutnya.

Teruskan Context ke Setiap Pemanggilan

Di server HTTP, r.Context() dibatalkan ketika client memutus koneksi atau server dimatikan. Teruskan context itu sebagai argumen pertama ke setiap fungsi yang melakukan I/O, dan pakai versi pemanggilan database dan HTTP yang menerima context, seperti QueryContext dan http.NewRequestWithContext. Dengan begitu request yang dibatalkan ikut menghentikan query database dan pemanggilan ke layanan lain, sehingga server tidak menyelesaikan pekerjaan yang hasilnya tidak akan dibaca siapa pun.

  • Pasang batas waktu dengan context.WithTimeout di sekitar pemanggilan ke layanan lain, dan selalu panggil fungsi cancel dengan defer.
  • Di dalam loop yang panjang, periksa ctx.Done() lewat select supaya loop bisa berhenti lebih awal.
  • Jangan simpan context di dalam struct. Teruskan per pemanggilan.
  • Pakai context.WithoutCancel untuk pekerjaan yang harus tetap selesai setelah response terkirim, misalnya menulis audit log.

Tunggu Goroutine dengan errgroup

sync.WaitGroup dipakai untuk menunggu goroutine, dan sejak Go 1.25 method Go-nya bisa menjalankan sekaligus mencatat goroutine dalam satu panggilan. Kalau goroutine-nya bisa gagal, golang.org/x/sync/errgroup biasanya lebih cocok. errgroup.WithContext mengembalikan sebuah group dan sebuah context. Begitu ada goroutine yang mengembalikan error, context dibatalkan supaya goroutine lain bisa berhenti, dan Wait mengembalikan error pertama.

Contoh pemakaian yang umum adalah memuat dashboard yang butuh data dari tiga layanan. Jalankan tiga g.Go, masing-masing menulis ke variabelnya sendiri, lalu panggil g.Wait. Halaman cukup menunggu selama pemanggilan yang paling lambat, dan satu kegagalan langsung membatalkan dua lainnya.

Batasi Jumlah Goroutine dengan Worker Pool

Satu goroutine per item masih aman untuk sepuluh item, tapi ambruk di seratus ribu item, biasanya karena koneksi database habis atau kena rate limit API lain. Batasi jumlah worker.

  1. 1Buat group dengan errgroup.WithContext lalu panggil g.SetLimit(n), dengan n disesuaikan dengan kemampuan sistem tujuan.
  2. 2Loop setiap item dan panggil g.Go untuk masing-masing. Go akan menunggu kalau sudah ada n goroutine yang berjalan, jadi backpressure didapat tanpa kode tambahan.
  3. 3Di dalam setiap goroutine, langsung kembali kalau ctx.Err() tidak nil.
  4. 4Kumpulkan hasil di slice berdasarkan posisi, atau kirim lewat channel yang dibaca satu goroutine.
  5. 5Panggil g.Wait dan tangani error-nya sekali.

Pakai Select untuk Timeout dan Pembatalan

Statement select menunggu beberapa operasi channel dan menjalankan yang paling dulu siap. Gabungkan channel hasil dengan ctx.Done() supaya goroutine bisa menyerah ketika pemanggilnya berhenti menunggu. Kalau goroutine mengirim hasil ke channel yang mungkin tidak lagi dibaca pemanggil, beri channel itu buffer satu supaya pengiriman tidak pernah tertahan dan goroutine bisa selesai.

Temukan Data Race Sebelum Terjadi di Produksi

  • Jalankan test dengan go test -race di CI. Race detector hanya menemukan race di kode yang memang dijalankan, jadi hasilnya paling bagus kalau test-nya ikut menjalankan jalur yang paralel.
  • Map biasa tidak aman untuk ditulis bersamaan. Go akan menghentikan program dengan error concurrent map writes. Lindungi dengan mutex.
  • Tutup channel hanya dari sisi pengirim, dan cukup sekali. Mengirim ke channel yang sudah ditutup akan panic.
  • Pasang recover di goroutine berumur panjang yang Anda jalankan sendiri. Panic di goroutine mana pun menghentikan seluruh program.
  • Pantau jumlah goroutine di metrik. Angka yang terus naik walaupun trafik sudah turun menandakan kebocoran, dan profil goroutine dari net/http/pprof menunjukkan di mana mereka tertahan.

Satu Bug yang Sudah Hilang

Sebelum Go 1.22, loop for memakai satu variabel yang sama untuk semua iterasi, sehingga goroutine yang dijalankan di dalam loop sering melihat nilai terakhir saja. Sejak Go 1.22, setiap iterasi punya variabelnya sendiri asalkan go.mod mencantumkan go 1.22 atau lebih baru. Kode lama masih banyak berisi trik item := item, dan trik itu aman dihapus setelah upgrade.

Menulis kode paralel di Go itu mudah. Pekerjaan sesungguhnya ada pada membuatnya berhenti dengan rapi.

Pelatihan Golang kami memakai satu hari penuh untuk pola-pola ini, dengan latihan yang sengaja dibuat bocor, race, dan deadlock, supaya engineer mengenali gejalanya sebelum menemuinya di produksi.

Poin penting

  • Pastikan tahu apa yang membuat goroutine selesai sebelum menjalankannya, kalau tidak goroutine itu akan bocor.
  • Pakai channel untuk serah terima data dan sinyal, lalu mutex atau atomic untuk state bersama.
  • Teruskan context sebagai argumen pertama ke setiap pemanggilan I/O supaya pembatalan sampai ke database dan layanan lain.
  • Pakai errgroup dengan SetLimit untuk menunggu goroutine, membatalkan saat error pertama, dan membatasi jumlahnya.
  • Jalankan go test -race di CI dan pantau jumlah goroutine di produksi.

Artikel terkait

Artikel lain tentang software development, AI, cloud, dan infrastruktur.

Tangan memilah formulir pajak dan struk di samping kalkulator
Pengembangan AI|

Otomatisasi Input Data Dokumen dengan AI: Invoice, Struk, dan Formulir

Cara AI membaca invoice, struk, KTP, dan formulir lalu mengubahnya menjadi data terstruktur, bagaimana OCR dan large language model bekerja bersama, kenapa validasi dan review manusia tetap penting, cara mengukur akurasi, dan hal yang perlu diperhatikan soal data pribadi.

Pemandangan udara pelabuhan peti kemas yang sibuk dengan crane dan tumpukan kontainer
DevOps|

Deploy Aplikasi ke Kubernetes dengan Helm: Panduan Praktis

Apa itu Helm chart, cara kerja values dan template, menyusun chart untuk beberapa environment, upgrade dan rollback dengan aman, menjauhkan secret dari Git, testing chart di CI, dan memilih antara Helm dan Kustomize.

Sedang mencari partner untuk pengembangan software?

Ceritakan proyek yang sedang Anda bangun, kebutuhan yang ingin diselesaikan, dan tantangan yang dihadapi. Kami dapat membantu membahas pendekatan teknis, scope, timeline, dan estimasi biaya.

Mulai diskusi