Petunjuk dan konteks
Sebuah batch processor menangani banyak objek secara konkuren. Goroutine induk menunggu semua tugas dan menghentikan pengiriman (dispatching) ketika sebuah tugas gagal atau context dibatalkan. Gunakan Go 1.25 sync.WaitGroup.Go untuk mendefinisikan siklus hidup tersebut, dan jelaskan hubungannya dengan Add, Done, serta Wait, kontrak panic-nya, propagasi error, dan batasan pembatalan. Ini adalah pertanyaan coding karena keterampilan intinya adalah perancangan siklus hidup tugas konkuren.
Apa yang dievaluasi oleh pewawancara
Pertama, apakah Anda mengikat pembuatan tugas ke penghitungan alih-alih menempatkan Add di tempat yang dapat memicu race condition dengan Wait.
Kedua, apakah Anda memahami kontrak WaitGroup.Go: fungsi tidak boleh memicu panic, dan counter akan didekremen ketika fungsi mengembalikan nilai; tidak ada propagasi error atau pembatalan yang disediakan.
Ketiga, apakah Anda dapat merancang konkurensi terbatas, menghentikan pengiriman, dan mengumpulkan hasil tanpa kebocoran, race condition, atau antrean tak terbatas.
Keempat, apakah Anda dapat mengidentifikasi kapan errgroup.WithContext lebih cocok daripada memperlakukan WaitGroup sebagai primitif orkestrasi penuh.
Kelima, apakah pengujian mencakup keberhasilan, pembatalan, perlindungan panic, dan race condition pada proses tunggu dengan versi Go serta kontrak CI yang eksplisit.
Pertanyaan klarifikasi awal
- Apakah compiler dikunci pada Go 1.25 atau lebih baru?
- Berapa batas konkurensinya, dan apakah input dapat berupa stream tanpa batas?
- Apakah error pertama harus membatalkan tugas-tugas lain (siblings), atau semua error harus dikumpulkan?
- Apakah tugas boleh memicu panic, dan jika tidak, layer mana yang melakukan pemulihan (recover)?
- Apakah hasil harus mempertahankan urutan input, dan apakah error memerlukan pengidentifikasi objek?
- Apakah panggilan downstream mendukung pembatalan context dan retry yang idempoten?
Jawaban 30 detik
"Saya menggunakan WaitGroup.Go untuk mengikat setiap eksekusi ke counternya dan semaphore untuk membatasi konkurensi. Setiap tugas memeriksa context dan menulis hasil atau error melalui kolektor yang terlindungi. WaitGroup hanya bertugas menunggu; ia tidak mempropagasi error atau pembatalan, sehingga error pertama harus secara eksplisit membatalkan context turunan. Jika pembatalan pada error pertama adalah kebijakan standar, saya akan menggunakan errgroup.WithContext. Titik masuk tugas harus memulihkan panic yang diizinkan menjadi error atau memperjelas kebijakan level proses. Pengujian mencakup pembatalan, puncak konkurensi, konvergensi, dan race condition."
Solusi terperinci
Langkah 1: Kunci kontrak API
Go 1.25 menambahkan Go(f func()) ke sync.WaitGroup. Ini memulai f dan melakukan hal yang setara dengan Done ketika f mengembalikan nilai; dokumentasi mensyaratkan agar f tidak memicu panic. Kunci versinya di go.mod, image CI, dan kakas lokal agar compiler versi lama tidak masuk ke dalam alur kerja secara diam-diam.
Langkah 2: Tempatkan batas konkurensi pada batas tugas
Gunakan buffered semaphore sebelum memanggil Go atau di dalam tugas. Jika pengiriman dapat terblokir, buat proses akuisisi dapat dibatalkan sehingga context yang dibatalkan tidak membuat dispatcher menunggu selamanya.
var wg sync.WaitGroup
sem := make(chan struct{}, 8)
for _, item := range items {
if err := ctx.Err(); err != nil { break }
sem <- struct{}{}
item := item
wg.Go(func() {
defer func() { <-sem }()
if ctx.Err() != nil { return }
process(item)
})
}
wg.Wait()Langkah 3: Definisikan channel error dan pembatalan
WaitGroup tidak menyimpan error dan tidak membatalkan tugas-tugas lain. Turunkan context yang dapat dibatalkan; tugas pertama yang mencatat error akan memanggil pembatalan (cancel). Gunakan channel atau mutex untuk kolektor, dan baca setelah Wait selesai untuk menghindari akses konkuren.
Langkah 4: Buat batas panic menjadi eksplisit
Jika layanan memperlakukan panic sebagai kegagalan tugas yang dapat dipulihkan, recover yang di-defer dapat mengubahnya menjadi error dengan stack trace dan memicu pembatalan. Jika panic mengindikasikan invariansi yang rusak, jangan pulihkan secara diam-diam; dokumentasikan logging, peringatan (alerting), dan perilaku keluar dari proses.
Langkah 5: Hindari race condition antara pengiriman dan proses tunggu
Jangan memanggil Wait selama goroutine lain masih dapat memanggil Go kecuali ada protokol siklus hidup yang menjamin keamanannya. Batch processor sebaiknya memiliki satu dispatcher yang menentukan kapan tidak ada lagi tugas yang ditambahkan, lalu memanggil Wait. Tugas rekursif dinamis harus menyatakan kapan pemanggilan Go bersarang diizinkan.
Langkah 6: Bandingkan dengan errgroup
errgroup.WithContext menyediakan error non-nil pertama, pembatalan turunan, dan batas opsional, sehingga cocok untuk fan-out permintaan di mana satu kegagalan menghentikan tugas lainnya. WaitGroup.Go cocok untuk tugas-tugas independen, agregasi error kustom, atau siklus hidup yang dikelola oleh komponen lain.
Langkah 7: Verifikasi invariansi
Pengujian harus membuktikan bahwa setiap tugas yang dijalankan pada akhirnya mengurangi counter; pembatalan menghentikan pengiriman baru; satu error memicu pembatalan satu kali; beban puncak tetap berada dalam batas; dan hasil berhenti berubah setelah Wait. Jalankan go test -race untuk mendeteksi race condition pada kolektor.
Contoh jawaban berkualitas tinggi
"Pertama-tama saya mengunci Go 1.25. Dispatcher mengakuisisi semaphore selama context masih aktif, kemudian memanggil wg.Go; tugas melepaskan semaphore, memeriksa pembatalan, dan menjalankan pekerjaan. WaitGroup hanya menyediakan proses tunggu siklus hidup, jadi saya menambahkan context turunan, channel error yang membawa ID objek, dan pembatalan one-shot untuk kebijakan error pertama. Jika panic dapat dipulihkan, saya mengubahnya menjadi error beserta stack-nya; jika tidak, penanganan tingkat proses tetap eksplisit. Saya menghentikan pengiriman sebelum Wait, menggabungkan hasil setelahnya, dan menjalankan go test -race. Untuk pembatalan pada error pertama ditambah pembatasan, saya akan memilih errgroup.WithContext."
Kesalahan umum
- Memperlakukan
WaitGroup.Gosebagai error group → error tidak dipropagasi → kumpulkan secara eksplisit atau gunakan errgroup. - Terblokir pada semaphore setelah pembatalan → dispatcher tidak dapat keluar → gunakan select pada context saat melakukan akuisisi.
- Membiarkan tugas memicu panic secara langsung → melanggar kontrak dan dapat menghentikan proses secara paksa → konversikan panic yang diizinkan atau gunakan kebijakan proses yang eksplisit.
- Melakukan append ke satu slice dari banyak goroutine → menyebabkan data race → gunakan channel, mutex, atau agregasi pasca-tunggu.
- Memanggil Wait sebelum pengiriman selesai → penambahan dinamis bertabrakan dengan proses tunggu → definisikan protokol penghentian pengiriman.
- Hanya memeriksa hitungan akhir → bug pembatalan dan puncak konkurensi menjadi tersembunyi → periksa setiap invariansi dan jalankan race detector.
- Mengabaikan versi Go → perilaku lokal dan CI menjadi berbeda → kunci modul, image, dan toolchain.
- Menggunakan WaitGroup untuk setiap urusan orkestrasi → retry, deadline, dan kebijakan error pertama menjadi tersebar → pilih errgroup atau scheduler khusus jika diperlukan.
Pertanyaan lanjutan
Pertanyaan lanjutan 1: Bisakah Go menerima fungsi yang memicu panic?
Dokumentasi resmi mensyaratkan agar fungsi tersebut tidak memicu panic. Jika panic merupakan kegagalan bisnis yang dapat dipulihkan, lakukan recover pada batas yang ditentukan dan kembalikan error; jika tidak, pertahankan semantik panic dan andalkan kebijakan pemulihan serta alerting tingkat proses.
Pertanyaan lanjutan 2: Bagaimana Anda menjamin tidak ada tugas yang dimulai setelah pembatalan?
Periksa ctx.Err() sebelum pengiriman, gunakan select yang dapat dibatalkan saat mengakuisisi semaphore, dan periksa context lagi saat masuk ke tugas. Pekerjaan yang sudah berjalan tetap bergantung pada API downstream yang mematuhi pembatalan.
Pertanyaan lanjutan 3: Kapan errgroup lebih baik?
Pilih errgroup.WithContext untuk propagasi error pertama, pembatalan tugas lain, dan proses tunggu yang terpadu. Pilih WaitGroup.Go untuk tugas-tugas independen atau agregasi error kustom.
Pertanyaan lanjutan 4: Bisakah Go dipanggil saat sedang menunggu?
Hanya dengan protokol siklus hidup yang mencegah counter mencapai nol sebelum pekerjaan baru ditambahkan. Batch normal harus menghentikan pengiriman sebelum Wait untuk menghindari race condition pada hitungan nol.
Pertanyaan lanjutan 5: Bagaimana Anda mempertahankan urutan hasil?
Tetapkan indeks pada setiap input dan biarkan tugas menulis ke slotnya atau mengirimkan hasil berindeks. Agregasikan berdasarkan indeks setelah menunggu; jangan berbagi slice append-only di antara tugas-tugas.
Pertanyaan lanjutan 6: Bagaimana Anda menguji pemulihan panic?
Sisipkan tugas yang memicu panic dan pastikan bahwa layer pemulihan menghasilkan error teridentifikasi, memicu pembatalan, dan membiarkan tugas lain konvergen. Uji alur tanpa pemulihan secara terpisah terhadap kebijakan pemantauan dan proses yang disepakati.