Goroutines dan Channels di Go
Memahami goroutine dan channel di Go β cara kerja konkurensi, komunikasi antar goroutine, serta buffered vs unbuffered channel.
Versi Bahasa Indonesia. Baca dalam Bahasa Inggris
Goroutines
Goroutine adalah thread ringan (lightweight thread) yang dikelola oleh runtime Go. Ini memungkinkan pemrograman konkuren dengan menjalankan banyak fungsi secara independen dan simultan.
- Goroutine jauh lebih ringan daripada thread tradisional.
- Mereka berbagi ruang alamat yang sama, membuatnya efisien.
- Goroutine dijalankan menggunakan keyword
godiikuti dengan pemanggilan fungsi.
Ringan
Goroutine di Go ringan karena dirancang lebih efisien daripada thread OS tradisional. Efisiensi ini berasal dari beberapa optimasi di tingkat runtime yang memungkinkan jutaan goroutine berjalan secara konkuren dengan overhead minimal.
- Mulai dengan stack kecil (sekitar
2 KB)- Thread OS biasanya mulai dengan
2 MB. - Stack yang lebih kecil berarti lebih sedikit memori yang dialokasikan di awal.
- Thread OS biasanya mulai dengan
- Dikelola oleh scheduler runtime Go di user space, bukan oleh sistem operasi.
- Go menggunakan model M:N scheduling, di mana M goroutine dimultipleks ke N thread OS.
- Scheduler memutuskan kapan harus menjeda dan melanjutkan goroutine.
- Context switching lebih cepat.
- Karena goroutine berada di level user, hanya beberapa register dan stack pointer yang perlu disimpan.
- Thread OS memerlukan context switching CPU penuh yang membuatnya lebih lambat.
- Komunikasi efisien menggunakan channel.
Karena Go menggunakan garbage collector, ada jeda singkat untuk GC melakukan full scanning. Untuk beberapa kasus, jeda singkat ini dapat berdampak besar jika kita menjalankan aplikasi latensi rendah dengan traffic tinggi.
Contoh
package main
import (
"fmt"
"time"
)
func printMessage(msg string) {
for i := 0; i < 5; i++ {
fmt.Println(msg)
time.Sleep(100 * time.Millisecond)
}
}
func main() {
go printMessage("Goroutine 1") // Berjalan di goroutine terpisah
go printMessage("Goroutine 2") // Goroutine lainnya
printMessage("Main Function") // Berjalan di thread utama
}- Fungsi printMessage dijalankan secara konkuren oleh beberapa goroutine.
- Fungsi main itu sendiri berjalan sebagai goroutine.
Channels
Channel adalah mekanisme yang digunakan goroutine untuk berkomunikasi satu sama lain dan menyinkronkan eksekusi mereka. Channel memungkinkan pertukaran data yang aman antar goroutine tanpa perlu locking eksplisit.
- Channel memiliki tipe; Anda menentukan tipe data yang ditransmisikan.
- Gunakan fungsi
makeuntuk membuat channel. - Gunakan
<-untuk mengirim (channel <- value) dan menerima (value := <-channel) data. - Channel bisa unbuffered (synchronous) atau buffered (asynchronous).
Contoh
package main
import (
"fmt"
"time"
)
func sendMessage(ch chan string) {
time.Sleep(5 * time.Second)
ch <- "Hello from Goroutine" // Kirim data ke channel
}
func main() {
ch := make(chan string) // Buat channel bertipe string
go sendMessage(ch) // Mulai goroutine
message := <-ch // Terima data dari channel
fmt.Println(message) // Output: Hello from Goroutine
}- Goroutine sendMessage mengirim data ke channel.
- Fungsi main menunggu untuk menerima data sebelum melanjutkan.
Interaksi Antara Goroutines dan Channels
- Mengirim Data: Satu goroutine dapat mengirim data ke channel.
- Menerima Data: Goroutine lain dapat memblokir hingga menerima data.
- Sinkronisasi: Unbuffered channel bertindak sebagai titik sinkronisasi, memastikan satu goroutine menunggu yang lain.
Contoh
package main
import "fmt"
// Worker function untuk menghitung jumlah
func calculateSum(numbers []int, result chan int) {
sum := 0
for _, num := range numbers {
sum += num
}
result <- sum // Kirim hasil ke channel
}
func main() {
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// Buat channel untuk mengkomunikasikan hasil
result := make(chan int)
// Bagi pekerjaan ke dua goroutine
go calculateSum(numbers[:len(numbers)/2], result) // Setengah pertama
go calculateSum(numbers[len(numbers)/2:], result) // Setengah kedua
// Terima hasil dari kedua goroutine
sum1 := <-result
sum2 := <-result
fmt.Println("Total Sum:", sum1+sum2) // Gabungkan hasil
}- Dua goroutine menghitung jumlah parsial secara konkuren.
- Hasil dikirim kembali melalui channel result.
- Fungsi main menunggu untuk menerima kedua
resultssebelum menggabungkannya.
Buffered vs. Unbuffered Channels
Unbuffered Channels
- Tipe default.
- Pengirim memblokir sampai penerima siap.
- Penerima memblokir sampai pengirim mengirim data.
- Menjamin sinkronisasi.
ch := make(chan int) // Unbuffered channelBuffered Channels
- Mengizinkan beberapa nilai untuk diantrekan dalam channel.
- Pengirim hanya memblokir ketika buffer penuh.
- Penerima hanya memblokir ketika buffer kosong.
ch := make(chan int, 5) // Buffered channel dengan kapasitas 5Channel untuk Mencegah Race Condition
Mari kita buat contoh sederhana dari data race di Go. Di sini kita ingin menaikkan variabel counter, tetapi variabel ini akan diakses oleh banyak goroutine.
var counter int
func increment() {
for range 1_000 {
counter++ // Race condition di sini
}
}
func main() {
for range 10 {
go increment() // 10 goroutine menaikkan secara konkuren
}
time.Sleep(1 * time.Second)
fmt.Println("Final Counter:", counter)
}β go run main.go
Final Counter: 6897
β go run main.go
Final Counter: 4626Seperti yang kita lihat di atas, program ini mengembalikan nilai counter yang salah. Ini karena setiap goroutine memodifikasi counter tanpa sinkronisasi, menyebabkan data race dan nilai yang tidak akurat.
Ini dapat diperbaiki dengan menggunakan channel.
func increment(ch chan int) {
for range 1_000 {
ch <- 1 // Kirim permintaan increment
}
}
func main() {
ch := make(chan int)
counter := 0
go func() {
for value := range ch {
counter += value // Perbarui counter dengan aman
}
}()
for range 10 {
go increment(ch)
}
time.Sleep(1 * time.Second)
close(ch)
fmt.Println("Final Counter:", counter)
}β go run main.go
Final Counter: 10000
β go run main.go
Final Counter: 10000Dengan menambahkan channel, kita memastikan bahwa hanya satu goroutine (receiver) yang memperbarui counter. Ini menjamin akses aman ke variabel counter dan mengembalikan nilai yang konsisten dan benar. Metode ini sejalan dengan slogan Go di bawah ini.
Jangan berkomunikasi dengan berbagi memori; sebaliknya, bagikan memori dengan berkomunikasi.
Harap diperhatikan bahwa contoh di atas sangat disederhanakan. Dalam skenario dunia nyata, Anda harus menggunakan sync.WaitGroup untuk menunggu semua goroutine selesai, bukan menggunakan time.Sleep.
Ringkasan
- Goroutine menyediakan konkurensi, sementara channel memfasilitasi komunikasi dan sinkronisasi.
- Channel mencegah race condition dengan memungkinkan data mengalir dengan aman antar goroutine.
- Gunakan unbuffered channel untuk sinkronisasi dan buffered channel untuk komunikasi yang lebih terdecouple.
Referensi
Last updated on