Ini adalah petikan siaran; siaran penuh tersedia di sini: https://victoriametrics.com/blog/go-sync-cond/
Siaran ini adalah sebahagian daripada siri tentang pengendalian concurrency dalam Go:
Dalam Go, sync.Cond ialah penyegerakan primitif, walaupun ia tidak seperti yang biasa digunakan seperti adik-beradiknya seperti sync.Mutex atau sync.WaitGroup. Anda jarang akan melihatnya dalam kebanyakan projek atau bahkan dalam perpustakaan standard, di mana mekanisme penyegerakan lain cenderung untuk menggantikannya.
Maksudnya, sebagai jurutera Go, anda sebenarnya tidak mahu mendapati diri anda membaca kod yang menggunakan penyegerakan.Cond dan tidak tahu apa yang sedang berlaku, kerana ia adalah sebahagian daripada perpustakaan standard, selepas semua.
Jadi, perbincangan ini akan membantu anda merapatkan jurang itu, dan lebih baik lagi, ia akan memberi anda gambaran yang lebih jelas tentang cara ia sebenarnya berfungsi dalam amalan.
Jadi, mari kita pecahkan apa itu sync.Cond.
Apabila goroutine perlu menunggu sesuatu yang khusus berlaku, seperti beberapa data yang dikongsi berubah, ia boleh "sekat", bermakna ia hanya menjeda kerjanya sehingga ia mendapat kebenaran untuk meneruskan. Cara paling asas untuk melakukan ini ialah dengan satu gelung, mungkin juga menambah masa. Tidur untuk mengelakkan CPU menjadi gila dengan sibuk menunggu.
Begini rupanya:
// wait until condition is true for !condition { } // or for !condition { time.Sleep(100 * time.Millisecond) }
Kini, ini tidak begitu cekap kerana gelung itu masih berjalan di latar belakang, terbakar melalui kitaran CPU, walaupun tiada apa-apa yang berubah.
Di situlah sync.Cond melangkah masuk, cara yang lebih baik untuk membenarkan goroutine menyelaraskan kerja mereka. Secara teknikal, ini adalah "pembolehubah keadaan" jika anda berasal dari latar belakang yang lebih akademik.
Berikut ialah penyegerakan antara muka asas.Cond menyediakan:
// Suspends the calling goroutine until the condition is met func (c *Cond) Wait() {} // Wakes up one waiting goroutine, if there is one func (c *Cond) Signal() {} // Wakes up all waiting goroutines func (c *Cond) Broadcast() {}
Baiklah, mari lihat contoh pseudo pantas. Kali ini, kami mempunyai tema Pokémon yang sedang berlangsung, bayangkan kami sedang menunggu Pokémon tertentu dan kami ingin memberitahu goroutine lain apabila ia muncul.
// wait until condition is true for !condition { } // or for !condition { time.Sleep(100 * time.Millisecond) }
Dalam contoh ini, satu goroutine sedang menunggu Pikachu muncul, manakala satu lagi (pengeluar) secara rawak memilih Pokémon daripada senarai dan memberi isyarat kepada pengguna apabila yang baharu muncul.
Apabila pengeluar menghantar isyarat, pengguna bangun dan menyemak sama ada Pokémon yang betul telah muncul. Jika ada, kami menangkap Pokémon, jika tidak, pengguna akan tidur semula dan menunggu yang seterusnya.
Masalahnya, terdapat jurang antara pengeluar yang menghantar isyarat dan pengguna sebenarnya bangun. Sementara itu, Pokémon boleh berubah, kerana goroutine pengguna mungkin bangun lewat daripada 1ms (jarang) atau goroutine lain mengubah suai pokemon yang dikongsi. Jadi sync.Cond pada asasnya berkata: 'Hei, sesuatu telah berubah! Bangun dan lihat, tetapi jika anda terlambat, ia mungkin berubah lagi.'
Jika pengguna bangun lewat, Pokémon mungkin lari, dan goroutine akan tidur semula.
"Hah, saya boleh menggunakan saluran untuk menghantar nama atau isyarat pokemon kepada goroutine yang lain"
Sudah tentu. Malah, saluran biasanya lebih diutamakan daripada penyegerakan.Cond in Go kerana ia lebih ringkas, lebih idiomatik dan biasa kepada kebanyakan pembangun.
Dalam kes di atas, anda boleh menghantar nama Pokémon dengan mudah melalui saluran, atau hanya menggunakan struct kosong{} untuk memberi isyarat tanpa menghantar sebarang data. Tetapi isu kami bukan hanya tentang menghantar mesej melalui saluran, ia mengenai menangani keadaan yang dikongsi.
Contoh kami agak mudah, tetapi jika berbilang goroutine mengakses pembolehubah pokemon yang dikongsi, mari lihat apa yang berlaku jika kami menggunakan saluran:
Yang berkata, apabila berbilang goroutine mengubah suai data kongsi, mutex masih diperlukan untuk melindunginya. Anda akan sering melihat gabungan saluran dan mutex dalam kes ini untuk memastikan penyegerakan yang betul dan keselamatan data.
"Baiklah, tetapi bagaimana pula dengan isyarat penyiaran?"
Soalan yang bagus! Anda sememangnya boleh meniru isyarat siaran kepada semua goroutine menunggu menggunakan saluran dengan hanya menutupnya (close(ch)). Apabila anda menutup saluran, semua gorout yang menerima daripada saluran itu akan dimaklumkan. Tetapi perlu diingat, saluran tertutup tidak boleh digunakan semula, sebaik sahaja ia ditutup, ia akan tetap ditutup.
Sebenarnya, sebenarnya ada cakap-cakap tentang mengalih keluar penyegerakan.Cond in Go 2: proposal: sync: alih keluar jenis Cond.
"Jadi, apakah penyegerakan. Jadi, bagus untuk Cond?"
Nah, terdapat senario tertentu di mana penyegerakan.Cond boleh menjadi lebih sesuai daripada saluran.
"Mengapa Kunci dibenamkan dalam penyegerakan.Cond?"
Secara teorinya, pembolehubah keadaan seperti sync.Cond tidak perlu diikat pada kunci untuk isyaratnya berfungsi.
Anda boleh meminta pengguna menguruskan kunci mereka sendiri di luar pembolehubah keadaan, yang mungkin terdengar seperti ia memberikan lebih fleksibiliti. Ia sebenarnya bukan had teknikal tetapi lebih kepada kesilapan manusia.
Menguruskannya secara manual boleh menyebabkan kesilapan dengan mudah kerana coraknya tidak begitu intuitif, anda perlu membuka kunci mutex sebelum memanggil Wait(), kemudian menguncinya semula apabila goroutine bangun. Proses ini boleh berasa janggal dan sangat terdedah kepada ralat, seperti terlupa untuk mengunci atau membuka kunci pada masa yang betul.
Tetapi kenapa coraknya kelihatan sedikit berbeza?
Biasanya, goroutine yang memanggil cond.Wait() perlu menyemak beberapa keadaan kongsi dalam gelung, seperti ini:
// wait until condition is true for !condition { } // or for !condition { time.Sleep(100 * time.Millisecond) }
Kunci tertanam dalam penyegerakan.Cond membantu mengendalikan proses kunci/buka kunci untuk kami, menjadikan kod lebih bersih dan kurang terdedah kepada ralat, kami akan membincangkan corak secara terperinci tidak lama lagi.
Jika anda melihat dengan teliti pada contoh sebelumnya, anda akan melihat corak yang konsisten dalam pengguna: kami sentiasa mengunci mutex sebelum menunggu (.Tunggu()) dengan syarat dan kami membuka kuncinya selepas syarat dipenuhi.
Selain itu, kami membungkus keadaan menunggu di dalam gelung, berikut adalah penyegar semula:
// Suspends the calling goroutine until the condition is met func (c *Cond) Wait() {} // Wakes up one waiting goroutine, if there is one func (c *Cond) Signal() {} // Wakes up all waiting goroutines func (c *Cond) Broadcast() {}
Apabila kami memanggil Wait() pada penyegerakan.Cond, kami memberitahu goroutine semasa untuk bertahan sehingga beberapa syarat dipenuhi.
Inilah yang berlaku di sebalik tabir:
Berikut ialah lihat bagaimana Wait() berfungsi di bawah hud:
// wait until condition is true for !condition { } // or for !condition { time.Sleep(100 * time.Millisecond) }
Walaupun mudah, kami boleh mengambil 4 perkara utama:
Disebabkan gelagat kunci/buka kunci ini, terdapat corak biasa yang akan anda ikuti apabila menggunakan penyegerakan.Cond.Wait() untuk mengelakkan kesilapan biasa:
// Suspends the calling goroutine until the condition is met func (c *Cond) Wait() {} // Wakes up one waiting goroutine, if there is one func (c *Cond) Signal() {} // Wakes up all waiting goroutines func (c *Cond) Broadcast() {}
"Mengapa tidak gunakan sahaja c.Wait() terus tanpa gelung?"
Ini adalah petikan siaran; siaran penuh tersedia di sini: https://victoriametrics.com/blog/go-sync-cond/
Atas ialah kandungan terperinci Pergi sync.Cond, Mekanisme Penyegerakan Yang Paling Diabaikan. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!