Rumah  >  Artikel  >  pembangunan bahagian belakang  >  Pergi sync.Cond, Mekanisme Penyegerakan Yang Paling Diabaikan

Pergi sync.Cond, Mekanisme Penyegerakan Yang Paling Diabaikan

DDD
DDDasal
2024-10-30 06:43:28301semak imbas

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:

  • Pergi sync.Mutex: Mod Normal dan Kebuluran
  • Pergi sync.WaitGroup dan Masalah Penjajaran
  • Segerakkan. Kolam dan Mekanik Di Belakangnya
  • Pergi sync.Cond, Mekanisme Penyegerakan Yang Paling Diabaikan (Kami di sini)
  • Pergi sync.Map: Alat yang Tepat untuk Kerja yang Tepat
  • Pergi Singleflight Lebur dalam Kod Anda, Bukan dalam DB Anda

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.

Apakah itu sync.Cond?

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.

  • Apabila satu goroutine sedang menunggu sesuatu berlaku (menunggu keadaan tertentu menjadi benar), ia boleh memanggil Tunggu().
  • Satu lagi goroutine, setelah mengetahui bahawa syarat itu mungkin dipenuhi, boleh menghubungi Signal() atau Broadcast() untuk membangunkan goroutine yang menunggu dan memberitahu mereka bahawa sudah tiba masanya untuk meneruskan.

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() {}

Go sync.Cond, the Most Overlooked Sync Mechanism

Ikhtisar penyegerakan.Cond

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:

  • Jika kami menggunakan saluran untuk menghantar nama Pokémon, kami masih memerlukan mutex untuk melindungi pembolehubah pokemon yang dikongsi.
  • Jika kami menggunakan saluran hanya untuk memberi isyarat, mutex masih diperlukan untuk mengurus akses kepada keadaan kongsi.
  • Jika kami menyemak Pikachu dalam penerbit dan kemudian menghantarnya melalui saluran, kami juga memerlukan mutex. Selain itu, kami akan melanggar prinsip pengasingan kebimbangan, di mana pengeluar mengambil logik yang benar-benar milik pengguna.

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.

  1. Dengan saluran, anda boleh sama ada menghantar isyarat kepada satu goroutine dengan menghantar nilai atau memberitahu semua goroutine dengan menutup saluran, tetapi anda tidak boleh melakukan kedua-duanya. sync.Cond memberi anda kawalan yang lebih halus. Anda boleh memanggil Signal() untuk membangunkan satu goroutine atau Broadcast() untuk membangunkan kesemuanya.
  2. Dan anda boleh memanggil Broadcast() seberapa banyak yang anda perlukan, saluran mana yang tidak boleh lakukan sebaik sahaja ia ditutup (menutup saluran tertutup akan mencetuskan panik).
  3. Saluran tidak menyediakan cara terbina dalam untuk melindungi data kongsi—anda perlu mengurusnya secara berasingan dengan mutex. sync.Cond, sebaliknya, memberikan anda pendekatan yang lebih bersepadu dengan menggabungkan penguncian dan isyarat dalam satu pakej (dan prestasi yang lebih baik).

"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.

Bagaimana cara menggunakannya?

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() {}

Cond.Wait()

Apabila kami memanggil Wait() pada penyegerakan.Cond, kami memberitahu goroutine semasa untuk bertahan sehingga beberapa syarat dipenuhi.

Inilah yang berlaku di sebalik tabir:

  1. Groutine akan ditambahkan pada senarai goroutine lain yang turut menunggu dalam keadaan yang sama ini. Semua gorouti ini disekat, bermakna mereka tidak boleh meneruskan sehingga mereka "dibangunkan" oleh sama ada panggilan Signal() atau Broadcast().
  2. Bahagian utama di sini ialah mutex mesti dikunci sebelum memanggil Wait() kerana Wait() melakukan sesuatu yang penting, ia secara automatik melepaskan kunci (memanggil Unlock()) sebelum meletakkan goroutine untuk tidur. Ini membolehkan goroutine lain mengambil kunci dan melakukan kerja mereka sementara goroutine asal sedang menunggu.
  3. Apabila goroutine yang menunggu dikejutkan (oleh Signal() atau Broadcast()), ia tidak akan meneruskan kerja dengan serta-merta. Pertama, ia perlu mendapatkan semula kunci (Kunci()).

Go sync.Cond, the Most Overlooked Sync Mechanism

Kaedah sync.Cond.Wait()

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:

  1. Terdapat penyemak untuk mengelakkan penyalinan contoh Cond, anda akan panik jika berbuat demikian.
  2. Memanggil cond.Wait() segera membuka kunci mutex, jadi mutex mesti dikunci sebelum memanggil cond.Wait(), jika tidak, ia akan panik.
  3. Selepas dikejutkan, cond.Wait() mengunci semula mutex, yang bermaksud anda perlu membukanya semula selepas anda selesai menggunakan data yang dikongsi.
  4. Kebanyakan fungsi sync.Cond dilaksanakan dalam masa jalan Go dengan struktur data dalaman yang dipanggil notifyList, yang menggunakan sistem berasaskan tiket untuk pemberitahuan.

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() {}

Go sync.Cond, the Most Overlooked Sync Mechanism

Corak biasa untuk menggunakan sync.Cond.Wait()

"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!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn