Program modern jarang bekerja sendirian: aplikasi web menunggu jawaban database, klien API menunggu balasan jaringan, dan proses latar belakang menulis berkas sambil melayani permintaan baru. Go menyelesaikan masalah penungguan tersebut dengan model concurrency yang ringan berbasis goroutine dan channel. Sebelum menyentuh sintaksnya, kita perlu meluruskan dua istilah yang sering tertukar: concurrency dan parallelism. Chapter ini membedah keduanya, menjelaskan cara runtime Go menjadwalkan goroutine, dan memperkenalkan GOMAXPROCS sebagai tombol utama pengatur parallelism.
Concurrency Bukan Parallelism #
Rob Pike, salah satu perancang Go, merangkum perbedaan kedua istilah tersebut dalam talk “Concurrency is not parallelism”: “Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once.” Concurrency adalah cara kita menyusun program menjadi bagian-bagian yang dapat berjalan secara independen; parallelism adalah eksekusi fisik beberapa bagian tersebut pada waktu yang sama.
Dua perbedaan kunci perlu ditegaskan. Pertama, concurrency tetap bekerja pada satu core CPU: scheduler berselang-seling menjalankan tugas-tugas tersebut sehingga tidak ada tugas yang menunggu lama tanpa kemajuan. Kedua, parallelism menuntut unit eksekusi paralel nyata, minimal dua core: tanpa core tambahan, tidak ada eksekusi bersamaan yang sesungguhnya. Sebuah program concurrent bisa berjalan paralel, tetapi belum tentu; program yang hanya parallel tanpa struktur concurrent justru jarang berguna karena sulit dikelola.
Penjadwalan M:N #
Runtime Go tidak membuat satu thread sistem operasi per goroutine. Runtime memakai penjadwalan M:N: ribuan goroutine (G) dijadwalkan ke sejumlah kecil thread OS (M) melalui processor (P) yang jumlahnya ditentukan GOMAXPROCS. Ketika sebuah goroutine menunggu — misalnya menerima data dari channel — scheduler menurunkannya dan mengangkat goroutine lain yang siap bekerja ke thread yang sama, tanpa melibatkan sistem operasi.
Diagram berikut memetakan hubungan ketiganya pada mesin dua core:
Pemetaan pada diagram tidak statis: goroutine berpindah antar processor, dan saat sebuah goroutine terblokir oleh operasi sistem yang lama, runtime dapat meminjam thread OS tambahan agar goroutine lain tetap jalan. Model inilah yang membuat goroutine jauh lebih murah daripada thread OS — pembahasan mendalam ada di chapter Goroutine .
GOMAXPROCS dan runtime.NumCPU #
Nilai GOMAXPROCS default sama dengan jumlah core logis yang terlihat oleh proses, yaitu hasil runtime.NumCPU(). Fungsi runtime.GOMAXPROCS(n) mengubah nilai tersebut, sedangkan runtime.GOMAXPROCS(0) hanya membaca nilai saat ini tanpa mengubahnya. Berkas concurrency-parallelism-demo.go pada folder chapter ini memperlihatkan ketiga perilaku tersebut:
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println("Jumlah core CPU :", runtime.NumCPU())
fmt.Println("GOMAXPROCS awal :", runtime.GOMAXPROCS(0))
runtime.GOMAXPROCS(1)
fmt.Println("GOMAXPROCS baru :", runtime.GOMAXPROCS(0))
}Output yang Dihasilkan:
Jumlah core CPU : 2
GOMAXPROCS awal : 2
GOMAXPROCS baru : 1Mari kita telaah baris demi baris program di atas:
- Baris 3–6 (
import ...): mengimpor packagefmtuntuk mencetak dan packageruntimeuntuk fungsi-fungsi runtime Go. - Baris 9 (
fmt.Println(... runtime.NumCPU())): membaca jumlah core logis CPU lalu mencetaknya; pada mesin uji chapter ini nilainya2. - Baris 10 (
runtime.GOMAXPROCS(0)): argumen0berarti “jangan mengubah nilai”, sehingga pemanggilan ini membacaGOMAXPROCSsaat ini, yaitu2sesuai defaultruntime.NumCPU(). - Baris 12 (
runtime.GOMAXPROCS(1)): mengubahGOMAXPROCSmenjadi1, sehingga paling banyak satu thread yang mengeksekusi kode Go secara bersamaan. - Baris 13: membaca kembali nilainya dan mencetak hasil
1sebagai bukti perubahan berhasil.
GOMAXPROCS membatasi jumlah thread yang mengeksekusi kode Go secara bersamaan, bukan jumlah goroutine yang boleh dibuat. Program dengan GOMAXPROCS(1) tetap boleh menjalankan ribuan goroutine; goroutine-goroutine tersebut hanya bergiliran memakai satu processor. Justru di situlah bukti concurrency berdiri sendiri terpisah dari parallelism.
time.Sleep Bukan Alat Concurrency #
Pemula sering memakai time.Sleep di main agar goroutine “sempat” selesai sebelum program keluar, atau menyelipkan jeda agar dua goroutine tidak bertabrakan. Kedua pola tersebut keliru: time.Sleep hanya menunda eksekusi dan tidak memberi jaminan apa pun tentang urutan maupun penyelesaian goroutine. Panjang tidur yang “cukup” pada laptop Anda bisa gagal pada server yang penuh beban. Sinkronisasi yang benar memakai channel atau tipe dari package sync, dan itulah alat utama yang akan kita kuasai sepanjang section ini.
Referensi #
- Concurrency is not parallelism — Rob Pike — talk asal kutipan perbedaan concurrency dan parallelism.
- runtime.GOMAXPROCS
— dokumentasi resmi fungsi
GOMAXPROCSdanNumCPU. - Share Memory By Communicating — Effective Go — filosofi concurrency Go.
Dengan istilah yang kini lurus, kita siap menyentuh alat konkretnya: keyword go yang menjalankan fungsi sebagai goroutine pada chapter berikutnya.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
concurrency-parallelism-demo.go
Unduh berkaspackage main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println("Jumlah core CPU :", runtime.NumCPU())
fmt.Println("GOMAXPROCS awal :", runtime.GOMAXPROCS(0))
runtime.GOMAXPROCS(1)
fmt.Println("GOMAXPROCS baru :", runtime.GOMAXPROCS(0))
}