Channel es un tipo central en Go. Puedes verlo como una tubería (pipe) a través de la cual las unidades centrales concurrentes pueden enviar o recibir datos para comunicarse (communication).
Su operador es la flecha<- 。
ch <- v // 发送值v到Channel ch中 v := <-ch // 从Channel ch中接收数据,并将数据赋值给v
(La dirección de la flecha es el flujo de los datos).
Al igual que los tipos de datos map y slice, un channel debe crearse antes de usarse:
ch := make(chan int)
Tipo Channel
El formato de definición del tipo Channel es el siguiente:
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
Incluye tres tipos de definición. El opcional<-representa la dirección del channel. Si no se especifica la dirección, entonces el Channel es bidireccional; puede tanto recibir datos como enviarlos.
chan T // 可以接收和发送类型为 T 的数据 chan<- float64 // 只可以用来发送 float64 类型的数据 <-chan int // 只可以用来接收 int 类型的数据
<-Siempre se asocia preferentemente con el tipo más a la izquierda.
chan<- chan int // 等价 chan<- (chan int) chan<- <-chan int // 等价 chan<- (<-chan int) <-chan <-chan int // 等价 <-chan (<-chan int) chan (<-chan int)
UsomakeInicializa el Channel y se puede establecer la capacidad:
make(chan int, 100)
La capacidad (capacity) representa el número máximo de elementos que puede contener el Channel, y representa el tamaño del búfer del Channel.
Si no se establece la capacidad, o si la capacidad se establece en 0, significa que el Channel no tiene búfer; solo cuando tanto el sender como el receiver están listos, ocurre su comunicación (communication) (Blocking). Si se establece un búfer, es posible que no se bloquee; solo cuando el buffer está lleno, send se bloquea, y solo cuando el búfer está vacío, receive se bloquea. Un channel nil no se comunica.
Mediante el método integradoclosese puede cerrar el Channel.
Puedes recibir/enviar (receive/send) datos desde/hacia un channel en múltiples goroutines, sin necesidad de considerar medidas de sincronización adicionales.
El Channel puede actuar como una cola FIFO (primero en entrar, primero en salir). El orden de los datos recibidos y el de los datos enviados es consistente.
El receive de un channel soportamulti-valued assignment, como `v, ok := <-ch`
v, ok := <-ch
Esto se puede usar para comprobar si el Channel ya ha sido cerrado.
- La sentencia send
La sentencia send se usa para enviar datos al Channel, comoch <- 3。
Su definición es la siguiente:
SendStmt = Channel "<-" Expression . Channel = Expression .
Antes de que comience la comunicación (communication), el channel y la expression deben evaluarse primero; por ejemplo, en el siguiente caso, (3+4) se calcula primero como 7 y luego se envía al channel.
c := make(chan int)
defer close(c)
go func() { c <- 3 + 4 }()
i := <-c
fmt.Println(i)
La comunicación (communication) se mantiene bloqueada hasta que el send se ejecuta (proceed). Como se dijo antes, en un channel sin búfer, el send solo se ejecuta cuando el receiver está listo. Si hay un búfer y el búfer no está lleno, el send se ejecutará.
Enviar datos a un channel que ya ha sido cerrado causará panic.run-time panic。
Enviar datos a un channel nil se bloqueará permanentemente.
- El operador receive
<-chSe usa para recibir datos del channel ch. Esta expresión se bloqueará (block) hasta que haya datos disponibles para recibir.
Recibir datos de un channel nil se bloqueará siempre.
Recibir datos de un channel cerrado no se bloquea, sino que retorna inmediatamente; después de recibir todos los datos ya enviados, devuelve el valor cero (zero value) del tipo del elemento.
Como se mencionó antes, puedes usar un parámetro de retorno adicional para comprobar si el channel está cerrado.
x, ok := <-ch x, ok = <-ch var x, ok = <-ch
Si OK es false, indica que la x recibida es el valor cero producido, y que este channel está cerrado o vacío.
blocking
Por defecto, el envío y la recepción se bloquean hasta que la otra parte esté lista. Este método se puede usar para sincronizar goroutines, sin necesidad de usar bloqueos explícitos o variables de condición.
Como en el ejemplo oficial,x, y := <-c, <-cEsta línea esperará hasta que el resultado del cálculo se envíe al channel.
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}Buffered Channels
El segundo parámetro de make especifica el tamaño del búfer:ch := make(chan int, 100)。
Mediante el uso del búfer, se puede evitar el bloqueo en la medida de lo posible y mejorar el rendimiento de la aplicación.
Range
for …… rangeLa sentencia puede procesar el Channel.
func main() {
go func() {
time.Sleep(1 * time.Hour)
}()
c := make(chan int)
go func() {
for i := 0; i < 10; i = i + 1 {
c <- i
}
close(c)
}()
for i := range c {
fmt.Println(i)
}
fmt.Println("Finished")
}
range cEl valor de iteración producido es el valor enviado en el Channel; iterará hasta que el channel sea cerrado. En el ejemplo anterior, si seclose(c)comenta,for …… rangeesa línea.
select
selectLa sentencia elige un conjunto de posibles operaciones send y receive para procesar. Es similar aswitch, pero solo se usa para manejar operaciones de comunicación (communication).
Sucasecase puede ser una sentencia send, o una sentencia receive, o tambiéndefault。
receiveLa sentencia puede asignar el valor a una o dos variables. Debe ser una operación receive.
Se permite como máximo undefault caseque puede colocarse en cualquier posición de la lista de casos, aunque la mayoría de las veces lo ponemos al final.
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
Si hay múltiples casos que procesar al mismo tiempo, por ejemplo, si hay múltiples channels que pueden recibir datos, entonces Go elegirá un caso de manera pseudoaleatoria (pseudo-random) para procesar. Si no hay ningún caso que procesar, entonces elegirádefaultpara procesar, sidefault caseexiste.default caseSi no hayselect, entonces
la sentencia select se bloqueará hasta que algún caso necesite ser procesado.
selectLa sentenciaswitches igual que la sentencia switch; no es un bucle, solo elegirá un caso para procesar. Si quieres procesar el channel continuamente, puedes agregar un bucle for infinito alrededor:
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
timeout
selectUna aplicación muy importante es el manejo de tiempos de espera (timeout). Como mencionamos antes, si no hay ningún caso que procesar, la sentencia select se bloqueará indefinidamente. En este momento, podemos necesitar una operación de tiempo de espera para manejar la situación de timeout.
En el siguiente ejemplo, enviaremos un dato al channel c1 después de 2 segundos, peroselectel tiempo de espera se establece en 1 segundo, por lo que imprimiremostimeout 1, en lugar deresult 1。
import "time"
import "fmt"
func main() {
c1 := make(chan string, 1)
go func() {
time.Sleep(time.Second * 2)
c1 <- "result 1"
}()
select {
case res := <-c1:
fmt.Println(res)
case <-time.After(time.Second * 1):
fmt.Println("timeout 1")
}
}
En realidad, esto utiliza el métodotime.Aftermétodo, que devuelve un<-chan Timecanal unidireccional de tipo `<-chan Time`; en el tiempo especificado, envía la hora actual al channel devuelto.
Timer y Ticker
Veamos dos Channels relacionados con el tiempo.
Un timer es un temporizador que representa un evento único en el futuro. Puedes decirle al timer cuánto tiempo quieres esperar; proporciona un Channel que entrega un valor de tiempo en ese momento futuro. En el siguiente ejemplo, la segunda línea se bloqueará durante aproximadamente 2 segundos, hasta que llegue el momento y continúe la ejecución.
timer1 := time.NewTimer(time.Second * 2)
<-timer1.C
fmt.Println("Timer 1 expired")
Por supuesto, si solo quieres esperar simplemente, puedes usartime.Sleeppara lograrlo.
También puedes usartimer.Stoppara detener el temporizador.
timer2 := time.NewTimer(time.Second)
go func() {
<-timer2.C
fmt.Println("Timer 2 expired")
}()
stop2 := timer2.Stop()
if stop2 {
fmt.Println("Timer 2 stopped")
}
tickerEs un temporizador que se activa periódicamente; envía un evento (la hora actual) al Channel en un intervalo (interval), y el receptor del Channel puede leer eventos del Channel a intervalos fijos. En el siguiente ejemplo, el ticker se activa cada 500 milisegundos; puedes observar el tiempo de salida.
ticker := time.NewTicker(time.Millisecond * 500)
go func() {
for t := range ticker.C {
fmt.Println("Tick at", t)
}
}()
Al igual que timer, el ticker también puedeStopdetenerse mediante el método. Una vez que se detiene, el receptor ya no recibirá datos del channel.
close
El método integrado close se puede usar para cerrar el channel.
Resumamos las operaciones del sender y del receiver después de que el channel se cierra.
Si el channel c ya está cerrado, continuar enviando datos a él causará panic.panic: send on closed channel:
import "time"
func main() {
go func() {
time.Sleep(time.Hour)
}()
c := make(chan int, 10)
c <- 1
c <- 2
close(c)
c <- 3
}
Pero de este channel cerrado no solo se pueden leer los datos ya enviados, sino que también se puede leer continuamente el valor cero:
c := make(chan int, 10) c <- 1 c <- 2 close(c) fmt.Println(<-c) //1 fmt.Println(<-c) //2 fmt.Println(<-c) //0 fmt.Println(<-c) //0
Pero si se utilizarangela lectura con range, el bucle for saldrá después de que el channel se cierre:
c := make(chan int, 10)
c <- 1
c <- 2
close(c)
for i := range c {
fmt.Println(i)
}
Mediantei, ok := <-cse puede ver el estado del Channel, para determinar si el valor es el valor cero o un valor leído normalmente.
c := make(chan int, 10)
close(c)
i, ok := <-c
fmt.Printf("%d, %t", i, ok) //0, false
Sincronización
Un channel se puede usar para la sincronización entre goroutines.
En el siguiente ejemplo, la goroutine main espera a que la goroutine worker complete la tarea mediante el channel done. Después de que la worker termina la tarea, solo necesita enviar un dato al channel para notificar a la goroutine main que la tarea se ha completado.
import (
"fmt"
"time"
)
func worker(done chan bool) {
time.Sleep(time.Second)
// 通知任务已完成
done <- true
}
func main() {
done := make(chan bool, 1)
go worker(done)
// 等待任务完成
<-done
}
Fuente: http://colobu.com/2016/04/14/Golang-Channels/