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:

go
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:

text
Jumlah core CPU : 2
GOMAXPROCS awal : 2
GOMAXPROCS baru : 1

Mari kita telaah baris demi baris program di atas:

  1. Baris 3–6 (import ...): mengimpor package fmt untuk mencetak dan package runtime untuk fungsi-fungsi runtime Go.
  2. Baris 9 (fmt.Println(... runtime.NumCPU())): membaca jumlah core logis CPU lalu mencetaknya; pada mesin uji chapter ini nilainya 2.
  3. Baris 10 (runtime.GOMAXPROCS(0)): argumen 0 berarti “jangan mengubah nilai”, sehingga pemanggilan ini membaca GOMAXPROCS saat ini, yaitu 2 sesuai default runtime.NumCPU().
  4. Baris 12 (runtime.GOMAXPROCS(1)): mengubah GOMAXPROCS menjadi 1, sehingga paling banyak satu thread yang mengeksekusi kode Go secara bersamaan.
  5. Baris 13: membaca kembali nilainya dan mencetak hasil 1 sebagai 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 #

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 berkas
go
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))
}