Model thread tradisional (contohnya, yang biasanya digunakan saat
menulis program Java, C++, Python) kadang mengharuskan pemrogram
untuk berkomunikasi antar thread menggunakan memori yang saling
dibagi.
Biasanya, dalam bentuk struktur data yang dilindungi oleh semacam
penguncian (lock), dan setiap thread tersebut akan bersaing
menggunakan kunci tersebut untuk mengakses data.
Pada kasus tertentu, hal ini dimudahkan dengan penggunaan struktur
data yang paham tentang thread, seperti Queue
pada Python.
Konkurensi primitif pada Go —goroutine dan channel— menyediakan sebuah solusi yang berbeda dan elegan untuk menulis perangkat lunak konkuren. (Konsep ini memiliki sejarah yang menarik yang dimulai dari tulisan C. A. R. Hoare tentang Communicating Sequential Processes.) Alih-alih secara eksplisit menggunakan kunci untuk menengahi akses terhadap data yang dibagi, Go mendorong penggunaan channel untuk mengirim referensi data antara goroutine. Pendekatan ini memastikan hanya satu goroutine yang memiliki akses terhadap data dalam satu waktu. Konsep ini disimpulkan dalam dokumen Efektif Go (yang harus dibaca oleh pemrogram Go).
Jangan berkomunikasi dengan berbagi memori; tapi, bagilah memori untuk berkomunikasi.
Perhatikan contoh program berikut yang memproses daftar URL. Dalam lingkungan model pemrograman thread tradisional, seseorang biasanya menulis struktur data seperti berikut:
type Resource struct { url string polling bool lastPolled int64 } type Resources struct { data []*Resource lock *sync.Mutex }
Dan kemudian sebuah fungsi Poller
(yang berjalan di thread yang
terpisah) bentuknya kurang lebih seperti berikut,
func Poller(res *Resources) { for { // ambil Resource yang terakhir dan tandai telah // diproses. res.lock.Lock() var r *Resource for _, v := range res.data { if v.polling { continue } if r == nil || v.lastPolled < r.lastPolled { r = v } } if r != nil { r.polling = true } res.lock.Unlock() if r == nil { continue } // proses URL... // perbarui Resource polling dan lastPolled. res.lock.Lock() r.polling = false r.lastPolled = time.Nanoseconds() res.lock.Unlock() } }
Fungsi ini hampir sehalaman panjangnya, dan membutuhkan lebih banyak detil lagi supaya selesai. Ia bahkan tidak mengikutkan logika untuk memproses URL (yang seharusnya cukup beberapa baris saja), dan bahkan tidak juga menangani kapan pengulangan berhenti.
Mari kita lihat fungsionalitas yang sama diimplementasikan dengan
idiom Go.
Pada contoh ini, Poller
adalah sebuah fungsi yang menerima
Resource
yang akan diproses dari sebuah channel masukan, dan
mengirimnya ke sebuah channel keluaran setelah selesai.
type Resource string func Poller(in, out chan *Resource) { for r := range in { // proses URL ... // kirim Resource yang telah diproses ke out. out <- r } }
Logika yang kompleks dari contoh sebelumnya sudah hilang, dan struktur
data Resource
kita sekarang tidak ada lagi mengurus penguncian data.
Malah, yang masih kurang adalah bagian yang paling penting,
pemrosesan.
Hal ini seharusnya memberikan Anda sebuah intuisi terhadap kekuatan
dari fitur bahasa yang sederhana.
Ada banyak yang kurang dari potongan kode di atas. Untuk langkah-langkah yang komplit, program Go yang idiomatis yang menggunakan gagasan tersebut, lihat lah Berbagi memori dengan berkomunikasi.