Prefacio
Aquellos que han oído hablar de él go
deben saber go
que su característica más importante goroutine
es la programación concurrente, y cuando se trata de programación concurrente, su uso channel
para la transmisión de datos es go
un curso obligatorio.
go
La filosofía de concurrencia: no comunicarse a través de la memoria compartida, sino implementar la memoria compartida a través de la comunicación.
channel
Hay muchas trampas, este artículo hablará brevemente sobre channel
el método para cerrarlas.
Principios básicos para cerrar canales.
channel
El principio de cierre ampliamente difundido :
No cierre desde el extremo receptor
channel
y no cierre activamente cuando haya varios remitentes.channel
El origen de este principio se debe a que:
channel
No se pueden enviar datos a cerrado- No se puede volver a cerrar un ya cerrado
channel
como cerrar
- El método más burdo es utilizar
defer-recovery
el mecanismo: si se bloquea al cerrarpanic
, también se bloquearárecovery
. - Dado que
channel
solo se puede usarclose
una vez,go
el paquete del código fuentesync.Once
puede resultar útil, específicamente para hacer este tipo de cosas.
A continuación, según el número de sender
y receiver
, se dividen las siguientes situaciones:
sender
Soltero Solteroreceiver
- Único
sender
y múltiplereceiver
sender
single largoreceiver
sender
Muchosreceiver
En los casos 1 y 2, simplemente sender
ciérrelo directamente por el final channel
.
func main() {
dataCh := make(chan int, 100)
// sender
go func() {
for i := 0; i < 1000; i++ {
dataCh <- i + 1
}
log.Println("send complete")
close(dataCh)
}()
// receiver
for i := 0; i < 5; i++ {
go func() {
for {
data, ok := <-dataCh
if !ok {
// 已关闭
return
}
_ = data
}
}()
}
select {
case <-time.After(time.Second * 5):
fmt.Println(runtime.NumGoroutine())
}
}
En el tercer caso, puede agregar una señal de apagado stopCh
y emitir una instrucción para cerrar los datos al receiver
final . Después de escuchar la señal de apagado, no se envían más datos a los datos.stopCh
dataCh
sender
dataCh
package main
import (
"time"
"math/rand"
"sync"
"log"
)
func main() {
rand.Seed(time.Now().UnixNano())
log.SetFlags(0)
const Max = 100000
const NumSenders = 1000
wgReceivers := sync.WaitGroup{
}
wgReceivers.Add(1)
dataCh := make(chan int)
stopCh := make(chan struct{
})
// senders
for i := 0; i < NumSenders; i++ {
go func() {
for {
select {
case <- stopCh:
return
default:
}
select {
case <- stopCh:
return
case dataCh <- rand.Intn(Max):
}
}
}()
}
// receiver
go func() {
defer wgReceivers.Done()
for value := range dataCh {
if value == Max-1 {
close(stopCh)
return
}
log.Println(value)
}
}()
wgReceivers.Wait()
}
receiver
El cuarto caso es más complicado y no se puede cerrar directamente por el extremo como el tercero stopCh
, lo que provocará que el channel
extremo cerrado se cierre repetidamente panic
. Por lo tanto, es necesario agregar un intermediario toStop
para recibir la stopCh
solicitud de cierre.
package main
import (
"time"
"math/rand"
"sync"
"log"
"strconv"
)
func main() {
rand.Seed(time.Now().UnixNano())
log.SetFlags(0)
const Max = 100000
const NumReceivers = 10
const NumSenders = 1000
wgReceivers := sync.WaitGroup{
}
wgReceivers.Add(NumReceivers)
dataCh := make(chan int)
stopCh := make(chan struct{
})
// 这个是添加的中间人,通过它来接收关闭 stopCh 的请求,做一次关闭
// 这里给缓存是 goroutine 启动时机,可能导致 select 选择,导致逻辑问题
toStop := make(chan string, 1)
var stoppedBy string
go func() {
stoppedBy = <-toStop
close(stopCh)
}()
// senders
for i := 0; i < NumSenders; i++ {
go func(id string) {
for {
value := rand.Intn(Max)
if value == 0 {
select {
case toStop <- "sender#" + id:
default:
}
return
}
// 由于 select 是随机选择的,所以先在这里尝试得知是否关闭
select {
case <- stopCh:
return
default:
}
select {
case <- stopCh:
return
case dataCh <- value:
}
}
}(strconv.Itoa(i))
}
// receivers
for i := 0; i < NumReceivers; i++ {
go func(id string) {
defer wgReceivers.Done()
for {
select {
case <- stopCh:
return
default:
}
select {
case <- stopCh:
return
case value := <-dataCh:
if value == Max-1 {
select {
case toStop <- "receiver#" + id:
default:
}
return
}
log.Println(value)
}
}
}(strconv.Itoa(i))
}
wgReceivers.Wait()
log.Println("stopped by", stoppedBy)
}
Este ejemplo puede enviar una señal de apagado tanto al final sender
como al final, pasar la señal de apagado a través de este intermediario y cerrarla después de recibirla . Cabe señalar aquí que se define como almacenado en búfer . Si no está almacenado en búfer, puede suceder que otras rutinas ya le hayan enviado señales de apagado antes de que la rutina receptora comience a ejecutarse. En este momento, la declaración puede tomarse en la rama o , lo que provocará errores lógicos.receiver
toStop
stopCh
toStop
channel
<-toStop
toStop<-xx
sender
receiver
select
default
En este ejemplo, un enfoque más sencillo puede ser toStop
establecer el caché en sender
la suma receiver
de y , que se puede abreviar de la siguiente manera:
...
toStop := make(chan string, NumReceivers + NumSenders)
...
value := rand.Intn(Max)
if value == 0 {
toStop <- "sender#" + id
return
}
...
if value == Max-1 {
toStop <- "receiver#" + id
return
}
...
Notas sobre canales
channel
La declaración debe usarmake
la palabra clave, no directamentevar c chan int
, entonces lo que obtienes esnil channel
- No se pueden
nil channel
enviar datos a
var c chan int
c <- 1 // panic
- Cerrado
channel
No se le pueden enviar más datos
c := make(chan int)
close(c)
c <- 1 // panic
- No se puede volver a cerrar un ya cerrado
channel
c := make(chan int)
close(c)
close(c) // panic
- Mientras
channel
no tengas una relación de referencia, incluso si noclose
la has cerrado ochannel
hay una gran cantidad de datos acumulados que no se han consumido, eventualmente se liberarángc
.
Resumir
channel
Reglas básicas para cerrar :
- En un solo
sender
caso, se puedesender
cerrar directamente en el lado del clientechannel
. - En muchos
sender
casos, puede agregar una señal de apagado quechannel
se utiliza específicamente para detener la transmisión de datoschannel
.
Principio: no cierre desde el extremo receptor channel
y no cierre activamente cuando hay varios remitenteschannel
.
Esencia: Lo que se ha cerrado channel
no se puede volver a cerrar (o se le pueden volver a enviar datos) .
channel
El uso de es muy diverso, este artículo enumera varios escenarios básicos, si realmente quieres entenderlo, aún tienes que escribir más y pensar más.