Rumah >pembangunan bahagian belakang >Golang >Pergi Saluran Dibuka Kunci: Cara Ia Berfungsi
Saluran Golang ialah komponen utama model konkurensi CSP dan jambatan untuk komunikasi antara Goroutines. Saluran kerap digunakan di Golang, dan amat penting untuk memahami prinsip pelaksanaan dalamannya. Artikel ini akan menganalisis pelaksanaan asas Saluran berdasarkan kod sumber Go 1.13.
Sebelum menganalisis secara rasmi pelaksanaan Saluran, mari semak penggunaan asasnya:
<code class="language-go">package main import "fmt" func main() { c := make(chan int) go func() { c <- 1 // 发送操作 }() x := <-c // 接收操作 fmt.Println(x) }</code>
Kod ini menunjukkan dua operasi asas Saluran:
c <- 1
x := <-c
Saluran dibahagikan kepada Saluran penimbal dan Saluran tidak penimbal. Kod di atas menggunakan Saluran bukan penimbal. Dalam Saluran tidak buffer, jika tiada Goroutine lain sedang menerima data, pengirim akan menyekat pada penyata hantar.
Anda boleh menentukan saiz penimbal apabila memulakan Saluran Sebagai contoh, make(chan int, 2)
menentukan saiz penimbal menjadi 2. Sebelum penimbal penuh, pengirim boleh menghantar data tanpa menyekat tanpa menunggu penerima bersedia. Tetapi jika penimbal penuh, pengirim akan tetap menyekat.
Sebelum menyelami kod sumber Saluran, anda perlu mencari lokasi pelaksanaan khusus Saluran di Golang. Apabila menggunakan Saluran, fungsi asas seperti runtime.makechan
, runtime.chansend
dan runtime.chanrecv
sebenarnya dipanggil.
Anda boleh menggunakan perintah go tool compile -N -l -S hello.go
untuk menukar kod kepada arahan pemasangan atau gunakan alat dalam talian Compiler Explorer (contohnya: go.godbolt.org/z/3xw5Cj). Dengan menganalisis arahan pemasangan, kita boleh menemui:
make(chan int)
sepadan dengan fungsi runtime.makechan
. c <- 1
sepadan dengan fungsi runtime.chansend
. x := <-c
sepadan dengan fungsi runtime.chanrecv
. Pelaksanaan fungsi ini terletak dalam runtime/chan.go
fail kod sumber Go.
make(chan int)
akan ditukar menjadi fungsi runtime.makechan
oleh pengkompil, dan tandatangan fungsinya adalah seperti berikut:
<code class="language-go">func makechan(t *chantype, size int) *hchan</code>
Antaranya, t *chantype
ialah jenis elemen Saluran, size int
ialah saiz penimbal yang ditentukan pengguna (0 jika tidak dinyatakan), dan nilai pulangan ialah *hchan
. hchan
ialah struktur pelaksanaan dalaman Saluran di Golang, ditakrifkan seperti berikut:
<code class="language-go">type hchan struct { qcount uint // 缓冲区中已放入元素的数量 dataqsiz uint // 用户构造Channel时指定的缓冲区大小 buf unsafe.Pointer // 缓冲区 elemsize uint16 // 缓冲区中每个元素的大小 closed uint32 // Channel是否关闭,==0表示未关闭 elemtype *_type // Channel元素的类型信息 sendx uint // 缓冲区中发送元素的索引位置(发送索引) recvx uint // 缓冲区中接收元素的索引位置(接收索引) recvq waitq // 等待接收的Goroutine列表 sendq waitq // 等待发送的Goroutine列表 lock mutex }</code>
Atribut dalam hchan
dibahagikan secara kasar kepada tiga kategori:
buf
, dataqsiz
, qcount
, dsb. Apabila saiz penimbal Saluran bukan 0, penimbal digunakan untuk menyimpan data yang akan diterima, dan dilaksanakan menggunakan penimbal cincin. recvq
mengandungi Goroutine menunggu untuk menerima data, sendq
mengandungi Goroutine menunggu untuk menghantar data. waitq
Dilaksanakan menggunakan senarai pautan berganda. lock
, elemtype
, closed
, dsb. makechan
terutamanya melaksanakan beberapa semakan kesahihan dan peruntukan memori atribut seperti penimbal dan hchan
, yang tidak akan dibincangkan secara mendalam di sini.
Berdasarkan analisis ringkas atribut hchan
, dapat dilihat bahawa terdapat dua komponen penting: penimbal dan giliran menunggu. Semua tingkah laku dan pelaksanaan hchan
berkisar pada dua komponen ini.
Proses penghantaran dan penerimaan Saluran adalah sangat serupa. Analisis dahulu proses penghantaran Saluran (contohnya c <- 1
).
cuba menghantar data ke Saluran, jika baris gilir recvq
tidak kosong, Goroutine yang menunggu untuk menerima data akan dikeluarkan daripada pengepala recvq
dan data akan dihantar terus ke Goroutine. Kodnya adalah seperti berikut:
<code class="language-go">package main import "fmt" func main() { c := make(chan int) go func() { c <- 1 // 发送操作 }() x := <-c // 接收操作 fmt.Println(x) }</code>
recvq
Mengandungi Goroutine menunggu untuk menerima data. Apabila Goroutine menggunakan operasi terima (seperti x := <-c
), jika sendq
tidak kosong pada masa ini, Goroutine akan diambil daripada sendq
dan data akan dihantar kepadanya.
Jika recvq
kosong, ini bermakna tiada Goroutine menunggu untuk menerima data pada masa ini dan Saluran akan cuba memasukkan data ke dalam penimbal:
<code class="language-go">func makechan(t *chantype, size int) *hchan</code>
Fungsi kod ini sangat mudah, ia adalah untuk meletakkan data ke dalam penimbal. Proses ini melibatkan pengendalian penimbal cincin, dataqsiz
mewakili saiz penimbal yang ditentukan pengguna (lalai kepada 0 jika tidak dinyatakan).
Jika Saluran bukan penimbal digunakan atau penimbal penuh (c.qcount == c.dataqsiz
), data yang akan dihantar dan Goroutine semasa akan dibungkus ke dalam objek sudog
, diletakkan dalam sendq
dan arus Goroutine akan ditetapkan untuk menunggu Status:
<code class="language-go">type hchan struct { qcount uint // 缓冲区中已放入元素的数量 dataqsiz uint // 用户构造Channel时指定的缓冲区大小 buf unsafe.Pointer // 缓冲区 elemsize uint16 // 缓冲区中每个元素的大小 closed uint32 // Channel是否关闭,==0表示未关闭 elemtype *_type // Channel元素的类型信息 sendx uint // 缓冲区中发送元素的索引位置(发送索引) recvx uint // 缓冲区中接收元素的索引位置(接收索引) recvq waitq // 等待接收的Goroutine列表 sendq waitq // 等待发送的Goroutine列表 lock mutex }</code>
goparkunlock
akan membuka kunci mutex input dan menggantung Goroutine semasa, menetapkannya kepada keadaan menunggu. gopark
dan goready
muncul secara berpasangan dan merupakan operasi timbal balik.
Dari perspektif pengguna, selepas memanggil gopark
, pernyataan kod untuk menghantar data akan disekat.
Proses penerimaan Saluran pada asasnya serupa dengan proses penghantaran, jadi saya tidak akan menerangkan butiran di sini. Operasi berkaitan penimbal yang terlibat dalam proses penerimaan akan diterangkan secara terperinci kemudian.
Perlu diambil perhatian bahawa keseluruhan proses penghantaran dan penerimaan Saluran dikunci menggunakan runtime.mutex
. runtime.mutex
ialah kunci ringan yang biasa digunakan dalam kod sumber berkaitan masa jalan Keseluruhan proses bukanlah penyelesaian tanpa kunci yang paling berkesan. Terdapat isu tentang Saluran tanpa kunci di Golang: go/issues#8899.
Saluran menggunakan penimbal cincin untuk menyimpan data bertulis. Penampan gelang mempunyai banyak kelebihan dan sesuai untuk melaksanakan baris gilir FIFO panjang tetap.
Pelaksanaan penimbal cincin dalam Saluran adalah seperti berikut:
Terdapat dua pembolehubah berkaitan penimbal dalamhchan
: recvx
dan sendx
. sendx
mewakili indeks boleh tulis dalam penimbal dan recvx
mewakili indeks boleh dibaca dalam penimbal. Elemen antara recvx
dan sendx
mewakili data yang telah dimasukkan ke dalam penimbal seperti biasa.
Anda boleh terus menggunakan buf[recvx]
untuk membaca elemen pertama baris gilir dan gunakan buf[sendx] = x
untuk meletakkan elemen di penghujung baris gilir.
Apabila penimbal tidak penuh, operasi memasukkan data ke dalam penimbal adalah seperti berikut:
<code class="language-go">package main import "fmt" func main() { c := make(chan int) go func() { c <- 1 // 发送操作 }() x := <-c // 接收操作 fmt.Println(x) }</code>
chanbuf(c, c.sendx)
bersamaan dengan c.buf[c.sendx]
. Proses di atas sangat mudah, hanya salin data ke lokasi penimbal sendx
.
Kemudian, gerakkan sendx
ke kedudukan seterusnya. Jika sendx
mencapai kedudukan terakhir, ia ditetapkan kepada 0, iaitu pendekatan hujung ke hujung biasa.
Apabila buffer tidak penuh, sendq
juga mesti kosong (kerana jika buffer tidak penuh, Goroutine yang menghantar data tidak akan beratur, tetapi akan terus memasukkan data ke dalam buffer). Pada masa ini, logik bacaan Saluran chanrecv
adalah agak mudah Data boleh dibaca terus dari penimbal Ia juga merupakan proses memindahkan recvx
, yang pada asasnya sama dengan penulisan penimbal di atas.
Apabila terdapat Goroutine menunggu dalam sendq
, penimbal mesti penuh pada masa ini. Pada masa ini, logik bacaan Saluran adalah seperti berikut:
<code class="language-go">func makechan(t *chantype, size int) *hchan</code>
ep
ialah alamat yang sepadan dengan pembolehubah yang menerima data (contohnya, dalam x := <-c
, ep
ialah alamat x
). sg
mewakili sendq
pertama yang diambil daripada sudog
. Dalam kod:
typedmemmove(c.elemtype, ep, qp)
bermaksud menyalin elemen yang boleh dibaca pada masa ini dalam penimbal ke alamat pembolehubah penerima. typedmemmove(c.elemtype, qp, sg.elem)
bermaksud menyalin data yang menunggu untuk dihantar oleh Goroutine dalam sendq
ke penimbal. Kerana recv
dilaksanakan kemudian, ia sama dengan meletakkan data dalam sendq
pada penghujung baris gilir. Ringkasnya, di sini Saluran menyalin data pertama dalam penimbal kepada pembolehubah penerima yang sepadan, dan pada masa yang sama menyalin elemen dalam sendq
ke penghujung baris gilir, dengan itu melaksanakan FIFO (masuk dahulu, keluar dahulu) .
Saluran ialah salah satu kemudahan yang paling biasa digunakan di Golang. Memahami kod sumbernya akan membantu anda menggunakan dan memahami Saluran dengan lebih baik. Pada masa yang sama, jangan terlalu percaya karut dan bergantung pada prestasi Saluran Reka bentuk semasa Saluran masih mempunyai banyak ruang untuk pengoptimuman.
Cadangan pengoptimuman:
Akhir sekali, saya mengesyorkan platform yang sangat sesuai untuk menggunakan perkhidmatan Go: Leapcell
Sila semak dokumentasi untuk maklumat lanjut!
Twitter Leapcell: https://www.php.cn/link/7884effb9452a6d7a7a79499ef854afd
Atas ialah kandungan terperinci Pergi Saluran Dibuka Kunci: Cara Ia Berfungsi. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!