通道(channel)完全可以与 goroutine(也可称go程)并驾齐驱。
通道类型的值本身是并发安全的,且使用十分方便。
声明:
1.第一个参数代表通道的具体类型的类型字面量;
确定通道类型的元素类型;
chan int :元素类型为int的通道类型;
chan string:元素类型为string的通道类型;
2.第二个参数可选,一个int类型的数值且大于0,表示通道的容量(通道缓存元素的个数)
容量=0时:非缓存的通道;
容量>0时:缓冲的通道;
非缓冲通道和缓冲通道的数据传递方式不同:
3.一个通道相当于一个先进先出队列。
元素值的发送和接收都要用到 <- 操作符 ,尖括号表示元素值的传输方向。
实例:
package main
import "fmt"
func main() {
ch1 := make(chan int, 3)//通道初始化,缓冲3个值
ch1 <- 2 //发送值到通道
ch1 <- 1
ch1 <- 3
elem1 := <-ch1 //从通道取值
fmt.Printf("The first element received from channel ch1: %v\n",
elem1)
}
4.对通道的发送和接收操作都有哪些基本的特性?
(1)对同一个通道,发送操作直接是互斥的,接收操作之间也是互斥的;
同一时刻,只能有一个发送操作;也只能有一个接收操作;
即:进队列时,只能一个一个进,出队列时,只能一个一个出。
这里并发执行指的是:多个代码块分别在不同的gouroutine中,有机会在同一时间执行;
细节:
a.元素从外界进入通道时会被复制,即:进入通道的并不是接收操作符右侧的那个值,而是它的副本。
b.元素值从通道进入外界时会被移动。第一步:生成正在通道中的这个元素的副本,并准备给接收方,第二步删除在通道中的元素值。
(2)发送操作和接收操作中,对元素值的处理都是不可分割的;
发送操作要么还没复制元素,要么已复制完毕;(原子操作)
接收操作在准备好元素值的副本后,一定会删除通道中原值;(原子操作)
(3)发送操作在完全完成前会被阻塞。接收操作也是如此;
发送操作 :a.复制元素值 b.放置副本到通道内部
在这两步完成前,发起发送操作的那个代码会被阻塞;
在通道完成发送操作后,运行时系统会通知代码所在的goroutine ,使它去争取继续运行的机会。
接收操作:a.复制通道内的元素; b.放置副本的接收方; c.删掉原值;
在这三步完成前,发起接收操作的那个代码会被阻塞;
在通道完成这些操作后,运行时系统会通知代码所在的goroutine,使它去争取继续执行的机会。
5.发送操作和接受操作什么时候可能被长时间的阻塞?
对于缓冲通道:
如果通道已满,对于它的所有发送操作会被阻塞,直到通道中有元素被取走。
此时,通道会优先通知最早等待的goroutine,后者会再次执行发送操作。
如果通道为空,对于它的接收操作会被阻塞,直到通道中有新的元素值出现。
此时,通道会优先通知最早等待接收的goroutine,并使它再次执行接收操作。
对于非缓冲通道:
无论发送操作和接收操作,一开始执行就会被阻塞,直到配对的操作也开始执行,才会继续传递。
即:非缓冲通道在用同步的方式传递数据。只有收发双方对接上才传递。
数据是直接从发送方复制给接收方,中间不用缓冲通道做中转。
相比之下,缓冲通道在用同步方式传递数据。
大多数情况下,缓冲通道会作为收发双方的中间件。但是,当发送操作发现通道为空,且正好等待的接收操作时,会直接把元素值复制给接收方。
由于错误引发的阻塞:
对于值为nil的通道,不论它的具体类型是什么,对于它的发送操作和接收操作都永久处于阻塞状态。
当只声明该类型的变量但没有使用make函数对它进行初始时,该类型变量就是nil。
示例:
package main
func main() {
// 示例1。
ch1 := make(chan int, 1)
ch1 <- 1
//ch1 <- 2 // 通道已满,因此这里会造成阻塞。
// 示例2。
ch2 := make(chan int, 1)
//elem, ok := <-ch2 // 通道已空,因此这里会造成阻塞。
//_, _ = elem, ok
ch2 <- 1
// 示例3。
var ch3 chan int
//ch3 <- 1 // 通道的值为nil,因此这里会造成永久的阻塞!
//<-ch3 // 通道的值为nil,因此这里会造成永久的阻塞!
_ = ch3
}
6.发送操作和接收操作在什么时候会引发 panic?
(1)对于一个已经初始化,但未关闭的通道来说,收发操作一定不会引发panic;
但是通道一旦关闭,再对它做发送操作,就会引发panic。
(2)关闭一个已经关闭的通道,也会引发painic.
当把接收表达式的结果同时赋给两个变量时,第二个变量类型一定是bool类型。它的值为false,说明通道已经关闭,并且没有元素可取了。
(3)如果通道关闭时,还有元素未取出,接收表达式第一个值,仍是通道中某一元素值,第二个结果值一定是true.
(4) 通过接收表达式的第二个结果值,来判断通道是否关闭是可能有延时的。关闭通道通常是由发送方来做。
示例:
package main
import "fmt"
func main() {
ch1 := make(chan int, 2)
// 发送方。
go func() {
for i := 0; i < 10; i++ {
fmt.Printf("Sender: sending element %v...\n", i)
ch1 <- i
}
fmt.Println("Sender: close the channel...")
close(ch1)
}()
// 接收方。
for {
elem, ok := <-ch1
if !ok {
fmt.Println("Receiver: closed channel")
break
}
fmt.Printf("Receiver: received an element: %v\n", elem)
}
fmt.Println("End.")
}
/*
Sender: sending element 0...
Sender: sending element 1...
Sender: sending element 2...
Sender: sending element 3...
Receiver: received an element: 0
Receiver: received an element: 1
Receiver: received an element: 2
Receiver: received an element: 3
Sender: sending element 4...
Sender: sending element 5...
Sender: sending element 6...
Sender: sending element 7...
Receiver: received an element: 4
Receiver: received an element: 5
Receiver: received an element: 6
Receiver: received an element: 7
Sender: sending element 8...
Sender: sending element 9...
Receiver: received an element: 8
Receiver: received an element: 9
Sender: close the channel...
Receiver: closed channel
*/