ir a lo básico del canal (cómo cerrar el canal con gracia)

Prefacio

Aquellos que han oído hablar de él godeben saber goque su característica más importante goroutinees la programación concurrente, y cuando se trata de programación concurrente, su uso channelpara la transmisión de datos es goun curso obligatorio.

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

channelHay muchas trampas, este artículo hablará brevemente sobre channelel método para cerrarlas.

Principios básicos para cerrar canales.

channelEl principio de cierre ampliamente difundido :

No cierre desde el extremo receptor channely no cierre activamente cuando haya varios remitentes.channel

El origen de este principio se debe a que:

  1. channelNo se pueden enviar datos a cerrado
  2. No se puede volver a cerrar un ya cerradochannel

como cerrar

  1. El método más burdo es utilizar defer-recoveryel mecanismo: si se bloquea al cerrar panic, también se bloqueará recovery.
  2. Dado que channelsolo se puede usar closeuna vez, goel paquete del código fuente sync.Oncepuede resultar útil, específicamente para hacer este tipo de cosas.

A continuación, según el número de sendery receiver, se dividen las siguientes situaciones:

  1. senderSoltero Solteroreceiver
  2. Único sendery múltiplereceiver
  3. sendersingle largoreceiver
  4. senderMuchosreceiver

En los casos 1 y 2, simplemente sendercié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 stopChy emitir una instrucción para cerrar los datos al receiverfinal . Después de escuchar la señal de apagado, no se envían más datos a los datos.stopChdataChsenderdataCh

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

receiverEl cuarto caso es más complicado y no se puede cerrar directamente por el extremo como el tercero stopCh, lo que provocará que el channelextremo cerrado se cierre repetidamente panic. Por lo tanto, es necesario agregar un intermediario toStop para recibir la stopChsolicitud 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 sendercomo 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.receivertoStopstopChtoStopchannel<-toStoptoStop<-xx
senderreceiverselectdefault

En este ejemplo, un enfoque más sencillo puede ser toStopestablecer el caché en senderla suma receiverde 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

  • channelLa declaración debe usar makela palabra clave, no directamente var c chan int, entonces lo que obtienes esnil channel
  • No se pueden nil channelenviar datos a
var c chan int
c <- 1 // panic
  • Cerrado channelNo se le pueden enviar más datos
c := make(chan int)
close(c)
c <- 1 // panic
  • No se puede volver a cerrar un ya cerradochannel
c := make(chan int)
close(c)
close(c) // panic
  • Mientras channelno tengas una relación de referencia, incluso si no closela has cerrado o channelhay una gran cantidad de datos acumulados que no se han consumido, eventualmente se liberarán gc.

Resumir

channelReglas básicas para cerrar :

  • En un solo sendercaso, se puede sendercerrar directamente en el lado del cliente channel.
  • En muchos sendercasos, puede agregar una señal de apagado que channelse utiliza específicamente para detener la transmisión de datos channel.

Principio: no cierre desde el extremo receptor channely no cierre activamente cuando hay varios remitenteschannel .

Esencia: Lo que se ha cerrado channelno se puede volver a cerrar (o se le pueden volver a enviar datos) .

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

referencia

Supongo que te gusta

Origin blog.csdn.net/DisMisPres/article/details/128655102
Recomendado
Clasificación