チャネルを閉じると、次の関数が実行されますclosechan
。
func closechan(c *hchan) {
// 关闭一个 nil channel,panic
if c == nil {
panic(plainError("close of nil channel"))
}
// 上锁
lock(&c.lock)
// 如果 channel 已经关闭
if c.closed != 0 {
unlock(&c.lock)
// panic
panic(plainError("close of closed channel"))
}
// …………
// 修改关闭状态
c.closed = 1
var glist *g
// 将 channel 所有等待接收队列的里 sudog 释放
for {
// 从接收队列里出队一个 sudog
sg := c.recvq.dequeue()
// 出队完毕,跳出循环
if sg == nil {
break
}
// 如果 elem 不为空,说明此 receiver 未忽略接收数据
// 给它赋一个相应类型的零值
if sg.elem != nil {
typedmemclr(c.elemtype, sg.elem)
sg.elem = nil
}
if sg.releasetime != 0 {
sg.releasetime = cputicks()
}
// 取出 goroutine
gp := sg.g
gp.param = nil
if raceenabled {
raceacquireg(gp, unsafe.Pointer(c))
}
// 相连,形成链表
gp.schedlink.set(glist)
glist = gp
}
// 将 channel 等待发送队列里的 sudog 释放
// 如果存在,这些 goroutine 将会 panic
for {
// 从发送队列里出队一个 sudog
sg := c.sendq.dequeue()
if sg == nil {
break
}
// 发送者会 panic
sg.elem = nil
if sg.releasetime != 0 {
sg.releasetime = cputicks()
}
gp := sg.g
gp.param = nil
if raceenabled {
raceacquireg(gp, unsafe.Pointer(c))
}
// 形成链表
gp.schedlink.set(glist)
glist = gp
}
// 解锁
unlock(&c.lock)
// Ready all Gs now that we've dropped the channel lock.
// 遍历链表
for glist != nil {
// 取最后一个
gp := glist
// 向前走一步,下一个唤醒的 g
glist = glist.schedlink.ptr()
gp.schedlink = 0
// 唤醒相应 goroutine
goready(gp, 3)
}
}
close のロジックは比較的単純で、チャネルの場合、recvq と sendq はブロックされた送信者と受信者をそれぞれ保存します。チャネルを閉じた後、待機中の受信者は、対応するタイプのゼロ値を受け取ります。送信者を待っている人は直接パニックになります。したがって、チャネル上に受信者がいるかどうかを知らずに、性急にチャネルを閉じることはできません。
close 関数は、まず大きなロックを取得し、次にこのチャネルにぶら下がっているすべての送信者と受信者を sudog リンク リストに接続し、ロックを解除します。最後に、すべての sdog を起動します。
起きたら、やるべきことは何でもやる。送信側は、chansend 関数の goparkunlock 関数の後もコードの実行を続行しますが、残念ながらチャネルが閉じられたことが検出され、パニックが発生します。受信者はさらに幸運で、いくつかの仕上げ作業を行った後に戻ってきます。ここで、selectedはtrueを返し、受け取った戻り値はチャネルが閉じているかどうかに応じて異なる値を返します。受信したチャネルが閉じている場合は false、それ以外の場合は true。私たちが分析したケースでは、受信した場合は false が返されます。
閉じたチャネルからデータを読み取ることはできますか?
バッファされたチャネルからデータを読み取ります。チャネルが閉じられていても、有効な値を読み取ることができます。返されたokがfalseの場合のみ、読み出したデータは無効となります。
func main() {
ch := make(chan int, 5)
ch <- 18
close(ch)
x, ok := <-ch
if ok {
fmt.Println("received: ", x)
}
x, ok = <-ch
if !ok {
fmt.Println("channel closed, data invalid.")
}
}
操作結果:
received: 18
channel closed, data invalid.
まずバッファリングされたチャネルを作成し、そこに要素を送信してから、チャネルを閉じます。チャネルからのデータの読み取りを 2 回試行した後でも、初回は値を正常に読み取ることができます。2 回目に返される ok は false で、チャネルが閉じられており、チャネル内にデータがないことを示します。
運営チャネルの概要
チャネルの運用結果を要約すると、次のようになります。
操作する | ゼロチャンネル | 閉じたチャネル | nil ではない、閉じられたチャネルではない |
---|---|---|---|
近い | パニック | パニック | 通常のシャットダウン |
読み取り <-ch | ブロック | 対応する型のゼロ値を読み取ります | 通常通りデータをブロックまたは読み取ります。バッファリングされたチャネルは、空の場合、またはバッファリングされていないチャネルが送信者を待っていない場合にブロックされます。 |
ライトチャンネル <- | ブロック | パニック | 通常どおりデータをブロックまたは書き込みます。待機中の受信者がいない場合、またはバッファされたチャネルのバッファがいっぱいの場合、バッファされていないチャネルはブロックされます。 |
要約すると、パニックが発生する状況は 3 つあります: 閉じたチャネルに書き込む場合、nil チャネルを閉じる場合、チャネルを繰り返し閉じる場合です。
nil チャネルの読み取りと書き込みはブロックされます。
チャネル上の要素の送受信の性質は何ですか?
チャネルの送信要素と受信要素の性質は何ですか?
移動チャネルでのすべての価値の転送は、価値のコピーを使用して行われます。
つまり、チャネルの送受信操作は、送信側ゴルーチンのスタックから chan buf へ、chan buf から受信側ゴルーチンへ、または送信側ゴルーチンから直接のいずれであっても、本質的には「値のコピー」です。受信側のゴルーチンに送信します。
例として:
type user struct {
name string
age int8
}
var u = user{name: "Ankur", age: 25}
var g = &u
func modifyUser(pu *user) {
fmt.Println("modifyUser Received Vaule", pu)
pu.name = "Anand"
}
func printUser(u <-chan *user) {
time.Sleep(2 * time.Second)
fmt.Println("printUser goRoutine called", <-u)
}
func main() {
c := make(chan *user, 5)
c <- g
fmt.Println(g)
// modify g
g = &user{name: "Ankur Anand", age: 100}
go printUser(c)
go modifyUser(g)
time.Sleep(5 * time.Second)
fmt.Println(g)
}
操作結果:
&{
Ankur 25}
modifyUser Received Vaule &{
Ankur Anand 100}
printUser goRoutine called &{
Ankur 25}
&{
Anand 100}
ここに良いshare memory by communicating
例があります。
最初に構造体 u が構築され、アドレスは 0x56420 で、その内容は図のアドレスの上にあります。次に、&u
値をポインタに代入しますg
。g のアドレスは 0x565bb0 で、その内容は u を指すアドレスです。
メイン プログラムでは、 g は最初に c に送信されます。 のcopy value
性質によれば、 chan buf に入るのは、0x56420
それがポインタ g の値 (ポインタ g が指す内容ではない) であるため、出力するときに、チャンネルです&{Ankur 25}
。したがって、ポインタ g はチャネルに「送信」されず、その値がコピーされるだけです。