Ir a la esencia de la entrevista lingüística: ¿cuál es el proceso de cierre de un canal?

Cerrar un canal ejecutará la función 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)
	}
}

La lógica del cierre es relativamente simple: para un canal, recvq y sendq almacenan el remitente y el receptor bloqueados respectivamente. Después de cerrar el canal, los receptores en espera recibirán un valor cero del tipo correspondiente. Aquellos que esperan al remitente, entrarán en pánico directamente. Por lo tanto, no puede cerrar el canal precipitadamente sin saber si hay receptores en el canal.

La función de cierre primero adquiere un bloqueo grande, luego conecta todos los remitentes y receptores colgados en este canal en una lista vinculada sudog y luego la desbloquea. Finalmente, despierta a todos los sudogs.

Después de despertarte, haz lo que tengas que hacer. El remitente continuará ejecutando el código después de la función goparkunlock en la función chansend, pero desafortunadamente detecta que el canal se ha cerrado y entra en pánico. El receptor tiene más suerte y regresa después de terminar un poco el trabajo. Aquí, seleccionado devuelve verdadero y el valor de retorno recibido devuelve valores diferentes dependiendo de si el canal está cerrado. recibido es falso si el canal está cerrado, verdadero en caso contrario. En el caso que analizamos, lo recibido devuelve falso.

¿Aún puedo leer datos de un canal cerrado?

Leer datos de un canal almacenado en búfer. Cuando el canal está cerrado, aún se pueden leer valores válidos. Solo cuando el ok devuelto es falso, los datos leídos no son válidos.

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

resultado de la operación:

received:  18
channel closed, data invalid.

Primero cree un canal almacenado en búfer, envíele un elemento y luego cierre el canal. Después de dos intentos de leer datos del canal, el valor aún se puede leer normalmente la primera vez. El ok devuelto por segunda vez es falso, lo que indica que el canal se ha cerrado y no hay datos en el canal.

Resumen de canales operativos

Para resumir los resultados de la operación del canal:

funcionar canal nulo canal cerrado no nulo, no canal cerrado
cerca pánico pánico Apagado normal
Leer <- cap bloquear Leer el valor cero del tipo correspondiente. Bloquear o leer datos normalmente. Un canal almacenado en búfer se bloqueará cuando esté vacío o un canal sin búfer no esté esperando al remitente.
escribe ch <- bloquear pánico Bloquea o escribe datos normalmente. Un canal sin búfer se bloqueará cuando no haya un receptor en espera o el canal almacenado en búfer esté lleno.

En resumen, hay tres situaciones en las que ocurre el pánico: escribir en un canal cerrado; cerrar un canal nulo; cerrar un canal repetidamente.

Se bloqueará la lectura y escritura de un canal nulo.

¿Cuál es la naturaleza del envío y recepción de elementos en un canal?
¿Cuál es la naturaleza de los elementos de envío y recepción del canal?

Toda transferencia de valor sobre los canales en movimiento ocurre con la copia de valor.

Es decir, las operaciones de envío y recepción del canal son esencialmente "copias de valores", ya sea desde la pila de la rutina del remitente al chan buf, o del chan buf a la rutina del receptor, o directamente desde la rutina del remitente. a la rutina del receptor.

Como ejemplo:

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

resultado de la operación:

&{
    
    Ankur 25}
modifyUser Received Vaule &{
    
    Ankur Anand 100}
printUser goRoutine called &{
    
    Ankur 25}
&{
    
    Anand 100}

He aquí un buen share memory by communicatingejemplo.

Insertar descripción de la imagen aquí

Al principio, se construye una estructura u, la dirección es 0x56420 y su contenido está encima de la dirección en la imagen. Luego asigne &uel valor al puntero g. La dirección de g es 0x565bb0 y su contenido es una dirección que apunta a u.

En el programa principal, g se envía primero a c. Según copy valuela naturaleza de , lo que entra en chan buf es que 0x56420es el valor del puntero g (no el contenido al que apunta), por lo que al imprimir los elementos recibidos del canal, lo es &{Ankur 25}. Por lo tanto, el puntero g no se "envía" al canal, sino que simplemente se copia su valor.

Supongo que te gusta

Origin blog.csdn.net/zy_dreamer/article/details/132795315
Recomendado
Clasificación