Rumah >pembangunan bahagian belakang >Golang >Pergi Saluran Dibuka Kunci: Cara Ia Berfungsi

Pergi Saluran Dibuka Kunci: Cara Ia Berfungsi

Mary-Kate Olsen
Mary-Kate Olsenasal
2025-01-17 02:11:10357semak imbas

Saluran Golang yang mendalam: Prinsip pelaksanaan dan cadangan pengoptimuman prestasi

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.

Penggunaan asas Saluran

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:

  • Kendalian hantar: c <- 1
  • Terima operasi: 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.

Fungsi pelaksanaan asas saluran

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.

Struktur saluran

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:

  • Atribut berkaitan penimbal: seperti buf, dataqsiz, qcount, dsb. Apabila saiz penimbal Saluran bukan 0, penimbal digunakan untuk menyimpan data yang akan diterima, dan dilaksanakan menggunakan penimbal cincin.
  • Atribut berkaitan baris gilir menunggu: recvq mengandungi Goroutine menunggu untuk menerima data, sendq mengandungi Goroutine menunggu untuk menghantar data. waitqDilaksanakan menggunakan senarai pautan berganda.
  • Atribut lain: seperti lock, elemtype, closed, dsb.
Fungsi

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.

Penghantaran data saluran

Proses penghantaran dan penerimaan Saluran adalah sangat serupa. Analisis dahulu proses penghantaran Saluran (contohnya c <- 1).

Apabila

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.

Terimaan data saluran

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.

Pelaksanaan penimbal cincin saluran

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 dalam

hchan: 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.

Go Channel Unlocked: How They Work

Anda boleh terus menggunakan buf[recvx] untuk membaca elemen pertama baris gilir dan gunakan buf[sendx] = x untuk meletakkan elemen di penghujung baris gilir.

Tulisan penimbal

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.

Bacaan penimbal

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

Ringkasan

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:

  • Gunakan mekanisme penguncian yang lebih ringan atau skema bebas kunci untuk meningkatkan prestasi.
  • Optimumkan pengurusan penimbal dan kurangkan peruntukan memori dan operasi penyalinan.

Leapcell: Platform tanpa pelayan terbaik untuk aplikasi web Golang

Go Channel Unlocked: How They Work

Akhir sekali, saya mengesyorkan platform yang sangat sesuai untuk menggunakan perkhidmatan Go: Leapcell

  1. Sokongan berbilang bahasa: Menyokong pembangunan JavaScript, Python, Go atau Rust.
  2. Gunakan projek tanpa had secara percuma: Bayar hanya untuk apa yang anda gunakan, tiada permintaan, tiada bayaran.
  3. Sangat menjimatkan kos: Bayar semasa anda pergi, tiada yuran terbiar. Contohnya: $25 menyokong 6.94 juta permintaan dengan purata masa tindak balas 60 milisaat.
  4. Pengalaman pembangun yang lancar: UI intuitif untuk persediaan mudah; saluran paip CI/CD automatik sepenuhnya dan metrik dan log masa nyata untuk cerapan yang boleh diambil tindakan.
  5. Skala mudah dan prestasi tinggi: Skala secara automatik untuk mudah mengendalikan overhed operasi sifar, fokus pada pembinaan.
Go Channel Unlocked: How They Work

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!

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
Artikel sebelumnya:OSD : Pilihan Kedua SayaArtikel seterusnya:tiada