Dasar-dasar Goroutine
Goroutine
Jika Anda meminta para Gopher untuk menjelaskan keuntungan golang, seringkali muncul artikel terkait konkurensi (Concurrency). Dasar dari konten tersebut adalah goroutine yang ringan dan mudah untuk ditangani. Saya telah menulis ringkasan singkat mengenai hal ini.
Konkurensi (Concurrency) vs Paralelisme (Parallelism)
Sebelum memahami goroutine, saya ingin membahas dua konsep yang sering membingungkan.
- Konkurensi: Konkurensi adalah tentang menangani banyak pekerjaan sekaligus. Ini tidak berarti harus benar-benar dieksekusi secara bersamaan, tetapi merupakan konsep struktural dan logis di mana beberapa tugas dibagi menjadi unit-unit kecil dan dieksekusi secara bergantian, sehingga bagi pengguna, terlihat seolah-olah beberapa tugas sedang diproses secara bersamaan. Konkurensi dimungkinkan bahkan pada single-core.
- Paralelisme: Paralelisme adalah "memproses beberapa pekerjaan secara bersamaan pada beberapa core". Ini berarti pekerjaan dilakukan secara paralel, menjalankan tugas-tugas yang berbeda secara bersamaan.
Goroutine memungkinkan implementasi konkurensi dengan mudah melalui Go runtime scheduler, dan secara alami memanfaatkan paralelisme melalui pengaturan GOMAXPROCS
.
Multi-thread Java yang sering digunakan dan memiliki utilisasi tinggi merupakan konsep paralelisme yang representatif.
Mengapa Goroutine Baik?
Ringan (lightweight)
Biaya pembuatan sangat rendah dibandingkan dengan bahasa lain. Muncul pertanyaan mengapa golang menggunakannya sesedikit itu? Karena lokasi pembuatannya dikelola di dalam Go runtime. Hal ini disebabkan karena merupakan logical thread yang ringan, lebih kecil dari unit OS thread, dan stack awal membutuhkan ukuran sekitar 2KB, serta dapat bervariasi secara dinamis dengan menambahkan stack sesuai implementasi pengguna.
Dengan pengelolaan berbasis stack, pembuatan dan penghapusan sangat cepat dan murah, sehingga jutaan goroutine dapat dijalankan tanpa membebani. Karena hal ini, Goroutine dapat meminimalkan intervensi OS kernel berkat runtime scheduler.
Performa baik (performance)
Pertama, Goroutine, seperti yang dijelaskan di atas, memiliki intervensi OS kernel yang minimal, sehingga biaya context switching pada user-level lebih murah daripada unit OS thread, memungkinkan peralihan tugas yang cepat.
Selain itu, ia dialokasikan dan dikelola pada OS thread menggunakan model M:N. Dengan membuat OS thread pool, pemrosesan dapat dilakukan dengan sedikit thread tanpa memerlukan banyak thread. Misalnya, jika terjadi status menunggu seperti system call, Go runtime akan menjalankan goroutine lain pada OS thread, sehingga OS thread tidak berhenti dan CPU dapat dimanfaatkan secara efisien untuk pemrosesan yang cepat.
Karena hal ini, Golang dapat mencapai performa yang lebih tinggi dalam operasi I/O dibandingkan bahasa lain.
Ringkas (concise)
Kemampuan untuk dengan mudah menangani fungsi dengan satu kata kunci go
ketika konkurensi diperlukan juga merupakan keuntungan besar.
Mutex
, Semaphore
, dan Lock yang kompleks harus digunakan, dan jika Lock digunakan, status deadlock yang harus dipertimbangkan secara inheren membuat fase desain pra-pengembangan menjadi kompleks.
Goroutine, sesuai filosofi "jangan berkomunikasi dengan berbagi memori, tetapi berbagi memori dengan berkomunikasi", merekomendasikan transfer data melalui channel
dan SELECT
mendukung fungsi yang memungkinkan pemrosesan dimulai dari channel yang datanya siap ketika digabungkan dengan channel
. Selain itu, dengan menggunakan sync.WaitGroup
, Anda dapat dengan mudah menunggu hingga semua goroutine selesai, sehingga aliran kerja dapat dikelola dengan mudah. Berkat alat-alat ini, masalah data race antar thread dapat dicegah dan pemrosesan konkurensi dapat dilakukan dengan lebih aman.
Selain itu, dengan menggunakan context, siklus hidup, pembatalan, timeout, deadline, dan cakupan permintaan dapat dikontrol pada user-level, sehingga tingkat stabilitas tertentu dapat dijamin.
Parallel Execution (GOMAXPROCS) Goroutine
Meskipun telah disebutkan keunggulan konkurensi goroutine, Anda mungkin bertanya-tanya apakah paralelisme tidak didukung. Jumlah core CPU saat ini berbeda dengan masa lalu, melebihi dua digit, dan PC rumahan juga memiliki jumlah core yang tidak sedikit.
Namun, Goroutine juga melakukan pekerjaan paralel, yaitu GOMAXPROCS
.
Jika GOMAXPROCS
tidak diatur, maka akan diatur secara berbeda tergantung versi.
Sebelum 1.5: Nilai default 1, jika diperlukan lebih dari 1, pengaturan seperti
runtime.GOMAXPOCS(runtime.NumCPU())
wajib dilakukan.1.5 ~ 1.24: Berubah menjadi semua logical core yang tersedia. Sejak saat itu, pengembang tidak perlu mengaturnya kecuali jika ada batasan signifikan yang diperlukan.
1.25: Sebagai bahasa yang terkenal di lingkungan kontainer, ia memeriksa cGroup di Linux untuk mengonfirmasi
batasan CPU
yang diatur pada kontainer.Jadi, jika jumlah logical core adalah 10 dan nilai batasan CPU adalah 5, maka
GOMAXPROCS
akan diatur ke angka yang lebih rendah yaitu 5.
Perubahan pada 1.25 memiliki dampak yang sangat besar. Ini karena utilitas bahasa di lingkungan kontainer telah meningkat. Hal ini mengurangi pembuatan thread yang tidak perlu dan context switching, sehingga mencegah CPU throttling.
1package main
2
3import (
4 "fmt"
5 "math/rand"
6 "runtime"
7 "time"
8)
9
10func exe(name int, wg *sync.WaitGroup) {
11 defer wg.Done()
12
13 fmt.Printf("Goroutine %d: ์์\n", name) // Goroutine %d: Mulai
14 time.Sleep(10 * time.Millisecond) // ์์
์๋ฎฌ๋ ์ด์
์ ์ํ ์ง์ฐ (Penundaan untuk simulasi pekerjaan)
15 fmt.Printf("Goroutine %d: ์์\n", name) // Goroutine %d: Mulai
16}
17
18func main() {
19 runtime.GOMAXPROCS(2) // CPU ์ฝ์ด 2๊ฐ๋ง ์ฌ์ฉ (Hanya menggunakan 2 core CPU)
20 wg := sync.WaitGroup();
21 goroutineCount := 10
22 wg.Add(goroutineCount)
23
24 for i := 0; i < goroutineCount; i++ {
25 go exe(i, &wg)
26 }
27
28 fmt.Println("๋ชจ๋ goroutine์ด ๋๋ ๋๊น์ง ๋๊ธฐํฉ๋๋ค...") // Menunggu semua goroutine selesai...
29 wg.Wait()
30 fmt.Println("๋ชจ๋ ์์
์ด ์๋ฃ๋์์ต๋๋ค.") // Semua pekerjaan telah selesai.
31
32}
33
Goroutine Scheduler (M:N Model)
Bagian sebelumnya yang menyatakan bahwa ia dialokasikan dan dikelola pada OS thread menggunakan model M:N akan dibahas lebih spesifik lagi dalam model goroutine GMP.
- G (Goroutine): Unit kerja terkecil yang dieksekusi di Go
- M (Machine): OS Thread (lokasi kerja sebenarnya)
- P (Processor): Proses logis yang dikelola oleh Go runtime
P juga memiliki local run queue tambahan, dan berfungsi sebagai scheduler yang menugaskan G yang dialokasikan ke M. Secara sederhana, goroutine adalah
Proses kerja GMP adalah sebagai berikut:
- Ketika G (Goroutine) dibuat, ia dialokasikan ke local run queue P (Processor).
- P (Processor) menugaskan G (Goroutine) yang ada di local run queue ke M (Machine).
- M (Machine) mengembalikan status G (Goroutine) yaitu block, complete, preempted.
- Work-Stealing: Jika local run queue P kosong, P lain akan memeriksa global queue. Jika tidak ada G (Goroutine) di sana, ia akan mencuri pekerjaan dari P (Processor) lokal lain untuk memastikan semua M terus bekerja tanpa henti.
- Penanganan System Call (Blocking): Jika G (Goroutine) mengalami block saat dieksekusi, M (Machine) akan masuk ke status menunggu. Pada saat ini, P (Processor) akan memisahkan diri dari M (Machine) yang terblokir dan bergabung dengan M (Machine) lain untuk mengeksekusi G (Goroutine) berikutnya. Pada saat ini, tidak ada pemborosan CPU bahkan selama waktu tunggu selama operasi I/O.
- Jika satu G (Goroutine) melakukan preemption terlalu lama, ia akan memberikan kesempatan eksekusi kepada G (Goroutine) lain.
GC (Garbage Collector) Golang juga berjalan di atas Goroutine, sehingga dapat membersihkan memori secara paralel dengan gangguan minimal pada eksekusi aplikasi (STW), sehingga sumber daya sistem dapat digunakan secara efisien.
Terakhir, Golang adalah salah satu keunggulan kuat bahasa ini, dan ada banyak lagi. Saya harap banyak pengembang dapat menikmati Golang.
Terima kasih.