GoSuda

Dasar-dasar Goroutine

By hamori
views ...

Goroutine

Apabila diminta untuk menguraikan keunggulan golang kepada Gopher, seringkali muncul artikel mengenai konkurensi (Concurrency). Landasan dari konsep ini adalah goroutine yang ringan dan dapat diproses dengan sederhana. Berikut ini adalah penjelasan singkat mengenai hal tersebut.

Konkurensi (Concurrency) vs Paralelisme (Parallelism)

Sebelum memahami goroutine, saya akan menjelaskan dua konsep yang seringkali membingungkan.

  • Konkurensi: Konkurensi berkaitan dengan pemrosesan banyak pekerjaan secara bersamaan. Ini tidak berarti bahwa pekerjaan tersebut benar-benar dieksekusi secara simultan, melainkan suatu konsep struktural dan logis di mana beberapa tugas dibagi menjadi unit-unit kecil dan dieksekusi secara bergantian, sehingga pengguna melihat seolah-olah beberapa tugas sedang diproses secara bersamaan. Konkurensi dapat dicapai bahkan pada inti tunggal (single core).
  • Paralelisme: Paralelisme adalah "pemrosesan beberapa pekerjaan secara simultan pada beberapa inti". Ini secara harfiah berarti melakukan pekerjaan secara paralel, mengeksekusi tugas-tugas yang berbeda secara bersamaan.

Goroutine memudahkan implementasi konkurensi melalui penjadwal runtime Go, dan secara alami memanfaatkan paralelisme melalui pengaturan GOMAXPROCS.

Multi-thread dalam Java, yang umumnya memiliki tingkat utilisasi tinggi, merupakan konsep representatif dari paralelisme.

Mengapa Goroutine Baik?

Ringan (lightweight)

Biaya pembuatannya sangat rendah dibandingkan dengan bahasa lain. Muncul pertanyaan mengapa golang menggunakannya secara hemat? Hal ini karena lokasi pembuatannya dikelola di dalam runtime Go. Alasannya adalah karena ini merupakan thread logis yang ringan seperti yang disebutkan di atas, lebih kecil dari unit thread OS, memerlukan stack awal berukuran sekitar 2KB, dan dapat berubah secara dinamis dengan menambahkan stack sesuai implementasi pengguna.

Karena dikelola dalam unit stack, pembuatan dan penghapusannya sangat cepat dan murah, sehingga memungkinkan pemrosesan jutaan goroutine tanpa beban yang berarti. Oleh karena itu, Goroutine dapat meminimalkan intervensi kernel OS berkat penjadwal runtime.

Kinerja yang baik (performance)

Pertama, Goroutine, seperti yang dijelaskan di atas, memiliki intervensi kernel OS yang minim, sehingga saat melakukan pengalihan konteks (context switching) pada tingkat pengguna (User-Level), biayanya lebih rendah daripada unit thread OS, memungkinkan peralihan tugas yang cepat.

Selain itu, Goroutine mengelola alokasi ke thread OS menggunakan model M:N. Dengan membuat kumpulan thread OS, pemrosesan dapat dilakukan dengan sedikit thread tanpa memerlukan banyak thread. Misalnya, jika Goroutine masuk ke kondisi menunggu seperti panggilan sistem (system call), runtime Go akan mengeksekusi goroutine lain pada thread OS tersebut, sehingga thread OS tidak beristirahat dan secara efisien memanfaatkan CPU untuk pemrosesan yang cepat.

Hal ini menyebabkan Golang dapat mencapai kinerja yang lebih tinggi dibandingkan bahasa lain, terutama dalam operasi I/O.

Ringkas (concise)

Salah satu keuntungan besar adalah kemampuan untuk dengan mudah menangani fungsi dengan satu kata kunci go ketika konkurensi diperlukan.

Penggunaan Mutex, Semaphore, dan Lock yang kompleks harus dilakukan, dan dengan penggunaan Lock, kondisi DeadLock yang harus dipertimbangkan secara inheren menjadi tidak terhindarkan, sehingga memerlukan tahapan yang kompleks sejak tahap desain sebelum pengembangan.

Goroutine menganjurkan transmisi data melalui Channel sesuai dengan filosofi "jangan berkomunikasi dengan berbagi memori, tetapi berbagi memori dengan berkomunikasi", dan SELECT mendukung fungsionalitas yang memungkinkan pemrosesan dimulai dari Channel yang datanya sudah siap saat digabungkan dengan Channel. Selain itu, dengan menggunakan sync.WaitGroup, kita dapat dengan mudah menunggu hingga semua goroutine selesai, sehingga aliran kerja dapat dikelola dengan mudah. Berkat alat-alat ini, masalah persaingan data antar thread dapat dicegah, dan pemrosesan konkurensi dapat dilakukan dengan lebih aman.

Selain itu, dengan memanfaatkan context, siklus hidup, pembatalan, timeout, deadline, dan cakupan permintaan dapat dikontrol pada tingkat pengguna (User-Level), sehingga menjamin tingkat stabilitas tertentu.

Pekerjaan Paralel Goroutine (GOMAXPROCS)

Meskipun telah disebutkan bahwa konkurensi goroutine sangat baik, mungkin muncul pertanyaan apakah paralelisme tidak didukung. Jumlah inti CPU saat ini, berbeda dengan masa lalu, melebihi dua digit, dan bahkan PC rumahan memiliki jumlah inti yang tidak sedikit.

Namun, Goroutine juga melakukan pekerjaan paralel, yaitu GOMAXPROCS.

Jika GOMAXPROCS tidak diatur, maka akan diatur berbeda tergantung versinya.

  1. Sebelum 1.5: Nilai default 1, pengaturan wajib diperlukan jika lebih dari 1, misalnya runtime.GOMAXPOCS(runtime.NumCPU()).

  2. 1.5 hingga 1.24: Diubah menjadi semua inti logis yang tersedia. Sejak saat itu, pengembang tidak perlu mengaturnya kecuali jika ada batasan yang sangat diperlukan.

  3. 1.25: Sebagai bahasa yang terkenal di lingkungan container, ia memeriksa cGroup di Linux untuk memverifikasi batas CPU yang ditetapkan pada container.

    Kemudian, jika jumlah inti logis adalah 10 dan nilai batas CPU adalah 5, GOMAXPROCS akan diatur ke angka yang lebih rendah, yaitu 5.

Perubahan pada versi 1.25 merupakan perubahan yang sangat besar. Hal ini karena utilitas bahasa di lingkungan container meningkat. Akibatnya, pembuatan thread yang tidak perlu dan pengalihan konteks berkurang, sehingga dapat 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) // Hanya menggunakan 2 inti 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

Penjadwal Goroutine (Model M:N)

Bagian sebelumnya menyebutkan alokasi dan pengelolaan ke thread OS menggunakan model M:N, dan secara lebih spesifik, ada model goroutine GMP.

  • G (Goroutine): Unit pekerjaan terkecil yang dieksekusi di Go.
  • M (Machine): Thread OS (lokasi pekerjaan sebenarnya).
  • P (Processor): Proses logis yang dikelola oleh runtime Go.

P juga memiliki antrian eksekusi lokal (Local Run Queue) dan berfungsi sebagai penjadwal yang menetapkan G yang dialokasikan ke M. Secara sederhana, goroutine

Proses kerja GMP adalah sebagai berikut:

  1. Ketika G (Goroutine) dibuat, ia dialokasikan ke antrean eksekusi lokal P (Processor).
  2. P (Processor) mengalokasikan G (Goroutine) di antrean eksekusi lokal ke M (Machine).
  3. M (Machine) mengembalikan status G (Goroutine) yaitu block, complete, preempted.
  4. Work-Stealing (Pencurian Pekerjaan): Jika antrean eksekusi lokal P kosong, P lain akan memeriksa antrean global. Jika tidak ada G (Goroutine) di sana juga, ia akan mencuri pekerjaan dari P (Processor) lokal lain untuk memastikan semua M terus beroperasi tanpa henti.
  5. Penanganan Panggilan Sistem (Blocking): Jika Block terjadi saat G (Goroutine) sedang 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.
  6. Jika satu G (Goroutine) menduduki (preempted) terlalu lama, kesempatan eksekusi akan diberikan kepada G (Goroutine) lain.

Golang juga menjalankan GC (Garbage Collector) di atas Goroutine, yang memungkinkan pembersihan memori secara paralel dengan gangguan minimum pada eksekusi aplikasi (STW), sehingga memanfaatkan sumber daya sistem secara efisien.

Terakhir, Golang adalah salah satu kekuatan utama bahasa ini, dan masih banyak lagi keunggulannya, jadi saya harap banyak pengembang akan menikmati Go.

Terima kasih.