Gosched
Goroutine adalah salah satu fitur paling kuat dalam bahasa Go, memungkinkan kita menjalankan ribuan tugas secara bersamaan dengan sangat ringan. Namun, pernahkah Anda bertanya-tanya: Siapa yang sebenarnya mengatur semua goroutine ini? Dan bagaimana kita bisa memastikan mereka semua mendapat giliran?
Artikel ini akan menyelami cara kerja Penjadwal (Scheduler) Go, membedakannya dari penjadwal Sistem Operasi (OS), dan secara khusus membahas fungsi runtime.Gosched() yang sering disalahpahami.
Dua Dunia Penjadwalan: OS vs. Go
Untuk memahami penjadwal Go, kita harus terlebih dahulu membedakannya dari penjadwal OS yang mungkin sudah lebih Anda kenal.
Penjadwal OS: Sang Diktator Preemptif
Penjadwal OS (Kernel-Level) berurusan dengan thread OS. Cara kerjanya preemptif, artinya ia memiliki kuasa penuh untuk menghentikan paksa sebuah thread yang sedang berjalan jika jatah waktunya (time slice) habis.
Proses ini diatur oleh interupsi jam (clock interrupt) dari perangkat keras. Ketika "alarm" berbunyi, OS mengambil alih, menyimpan kondisi thread saat ini, dan memilih thread lain dari antrian untuk dijalankan.
Penjadwal Go: Koordinator User-Space
Penjadwal Go berjalan di dalam program Go Anda sendiri (di "user-space"), bukan di dalam kernel OS. Ini membuatnya sangat efisien, tetapi juga berarti ia tidak bisa diinterupsi paksa oleh jam sistem seperti penjadwal OS.
Penjadwal Go bersifat kooperatif dan bergantung pada kejadian di level pengguna (user-level events) untuk tahu kapan harus beralih konteks (context switch) dari satu goroutine ke goroutine lainnya.
Pemicu di Balik Layar: Kapan Penjadwal Go Bekerja?
Penjadwal Go tidak akan aktif kecuali salah satu dari kejadian berikut memicunya:
Memulai goroutine baru (menggunakan kata kunci
go).Melakukan system call (operasi yang butuh "bantuan" OS, seperti membaca file atau menunggu koneksi jaringan).
Sinkronisasi goroutine (seperti mengirim atau menerima data melalui channel, atau menggunakan mutex).
Ketika salah satu event ini terjadi, Penjadwal Go "bangun" dan melihat apakah ada goroutine lain yang siap dijalankan.
runtime.Gosched(): Memberi Kesempatan pada yang Lain
runtime.Gosched(): Memberi Kesempatan pada yang LainBagaimana jika sebuah goroutine sedang sibuk melakukan perhitungan panjang tanpa memicu salah satu event di atas? Goroutine lain bisa "kelaparan" dan tidak mendapat giliran.
Di sinilah runtime.Gosched() berperan.
Dalam bahasa teknis, memanggil runtime.Gosched() berarti yield (menyerah). Goroutine yang sedang berjalan secara sukarela berkata kepada penjadwal, "Saya menyerahkan jatah waktu CPU saya. Silakan jalankan goroutine lain jika ada yang menunggu."
Studi Kasus: main vs sayHello
main vs sayHelloPerhatikan kode sederhana berikut:
package main
import (
"fmt"
"runtime"
)
func sayHello() {
fmt.Println("Hello")
}
func main() {
go sayHello() // (A) Memulai goroutine baru
// Tanpa baris di bawah, program bisa langsung selesai
runtime.Gosched()
fmt.Println("Finished") // (B) Selesai
}Masalahnya: Program Go akan berhenti segera setelah goroutine main() selesai. Ada kemungkinan besar goroutine main (B) berjalan begitu cepat dan selesai sebelum Penjadwal Go sempat memberi giliran pada sayHello (A). Hasilnya, program selesai tanpa pernah mencetak "Hello".
Solusinya: Dengan menambahkan runtime.Gosched(), kita secara eksplisit menyuruh main untuk "mundur" sejenak. Ini memberi kesempatan pada Penjadwal Go untuk melihat antrian dan (kemungkinan besar) menjalankan sayHello().
Peringatan Keras: Gosched() Bukanlah Jaminan
Gosched() Bukanlah JaminanIni adalah poin paling penting yang harus dipahami: runtime.Gosched() hanyalah sebuah saran, bukan perintah.
Peringatan: Kita tidak memiliki kendali atas goroutine mana yang akan dipilih oleh penjadwal selanjutnya.
Ketika Anda memanggil runtime.Gosched(), penjadwal bisa saja memilih goroutine lain, atau ia bisa memutuskan untuk menjalankan kembali goroutine yang baru saja Anda "serahkan".
Memanggil runtime.Gosched() hanya meningkatkan kemungkinan goroutine lain akan dieksekusi, tetapi tidak menjaminnya.
Sebagai programmer, kita tidak boleh menulis kode yang bergantung pada urutan penjadwalan. Jika Anda menjalankan kode di atas berulang kali, mungkin ada satu dari sekian banyak percobaan yang gagal mencetak "Hello" meskipun sudah menggunakan Gosched.
Jika Anda benar-benar perlu mengontrol urutan atau memastikan satu tugas selesai sebelum tugas lain, gunakan mekanisme sinkronisasi yang tepat (seperti Channels atau sync.WaitGroup), bukan bergantung pada runtime.Gosched().
Kasus Khusus: runtime.LockOSThread()
runtime.LockOSThread()Go juga menyediakan fungsi runtime.LockOSThread(). Fungsi ini "mengunci" sebuah goroutine agar berjalan secara eksklusif pada satu thread OS tertentu. Tidak ada goroutine lain yang akan dijadwalkan di thread itu sampai kuncinya dilepas (runtime.UnlockOSThread()).
Fungsi ini jarang sekali dibutuhkan. Penggunaan umumnya adalah ketika berinteraksi dengan library C eksternal yang mengharuskan semua pemanggilan fungsi berasal dari thread OS yang sama persis.
Kesimpulan
Penjadwal Go adalah sistem kooperatif berbasis event yang sangat efisien. runtime.Gosched() adalah alat yang kita miliki untuk secara sukarela menyerahkan giliran eksekusi, tetapi ia tidak memberikan jaminan apa pun. Untuk kode concurrent yang andal dan dapat diprediksi, selalu andalkan mekanisme sinkronisasi eksplisit.
Last updated