通道
1、简介
通道类型的值本身就是并发安全的,这也是Golang语言自带的,唯一一个可以满足并发安全性的类型; channel称为管道,本质是一个先进先出的队列; 使用goroutine+channel进行数据通信简单高效,同时线程安全,多个goroutine可同时修改一个channel,不需要加锁。
2、类型
2.1、收发角度分类
chanel分为三种类型
//只读 channel, 只能读channel里面的数据,不可写入
var readOnlyChan <- chan int
//只写channel,只能写入数据,不可读
var writeOnlyChan chan<- int
//可读可写,一般channel,可以读可以写
var mychan chan int
2.2、是否有缓冲角度分类
// 缓存通道
data := make(chan int, 3)
// 非缓存通道:
data := make(chan int)
3、对通道的发送和接收操作有哪些特性特性
- 对于同一个通道,发送操作之间是互斥的,接收操作也是互斥的
- 发送操作和接收操作中对元素值的处理都是不可分割的
- 发送操作在完全完成之前会被阻塞,接收操作也是如此
4、什么情况会出现阻塞?
4.1、对于缓冲通道
对于缓冲通道,如果通道已满,那么对它的所有发送操作都会被阻塞,直到通道中有元素值被接收;
func main() {
data := make(chan int, 1)
data <- 1
log.Println("向缓冲通道写入")
data <- 2
}
// output
2020/08/04 21:19:14 向缓冲通道写入
fatal error: all goroutines are asleep - deadlock!
如果通道已经为空,那么对它的所有接收操作都被会阻塞,直到通道中有新的元素出现;
func main() {
data := make(chan int, 1)
log.Println("从缓冲通道读取")
<-data
}
// output
2020/08/04 21:24:58 从缓冲通道读取
fatal error: all goroutines are asleep - deadlock!
4.2、对于非缓冲通道
通道中无数据,执行读通道
func main() {
data := make(chan int)
<-data
log.Println("从非缓冲通道读取")
}
// output
fatal error: all goroutines are asleep - deadlock!
通道中无数据,向通道写数据,无协程读取
func main() {
data := make(chan int)
data <- 1
log.Println("向非缓冲通道写入")
}
// output
fatal error: all goroutines are asleep - deadlock!
4.3、对于值为nil的通道
无论他的具体类型是什么,对它的发送操作,和接收操作都会永久的处于阻塞状态;所属的goroutine中的任何代码,都不再会执行。
func main() {
var data chan int
data <- 1
log.Println("空的通道")
}
// output
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send (nil chan)]:
5、select
5.1、简介
select是执行选择操作的一个结构,包含一组case语句,会执行其中无阻塞的某一个case,如果全部阻塞了,那就等待其中一个不阻塞进而继续执行;还有一个defalut语句,default语句永远不会阻塞,可以借助它实现无阻塞操作。
5.2、使用select解决非缓冲通道读取的阻塞问题
func main() {
data := make(chan int)
if v, err := readWithSelect(data); err != nil {
log.Println(err)
} else {
log.Println("read", v)
}
}
func readWithSelect(ch chan int) (int, error) {
select {
case x := <-ch:
return x, nil
default:
return 0, errors.New("channel has no data")
}
}
// output
2020/08/05 09:58:39 channel has no data
5.3、使用select解决缓冲通道读取的阻塞问题
func main() {
data := make(chan int, 1)
if v, err := readWithSelect(data); err != nil {
log.Println(err)
} else {
log.Println("read", v)
}
}
func readWithSelect(ch chan int) (int, error) {
select {
case x := <-ch:
return x, nil
default:
return 0, errors.New("channel has no data")
}
}
// output
2020/08/05 10:04:06 channel has no data
5.4、使用select解决非缓冲通道写入的阻塞问题
func main() {
data := make(chan int)
if err := writeWithSelect(data); err != nil {
log.Println(err)
} else {
log.Println("write success")
}
}
// writeWithSelect
// 通过select进行写入操作
func writeWithSelect(ch chan int) error {
select {
case ch <- 1:
return nil
default:
return errors.New("The pipe is full")
}
}
// outputt
2020/08/05 10:10:58 The pipe is full
5.5、使用select解决缓冲通道写入的阻塞问题
func main() {
data := make(chan int, 1)
data <- 1
if err := writeWithSelect(data); err != nil {
log.Println(err)
} else {
log.Println("write success")
}
}
// writeWithSelect
// 通过select进行写入操作
func writeWithSelect(ch chan int) error {
select {
case ch <- 1:
return nil
default:
return errors.New("The pipe is full")
}
}
// output
2020/08/05 10:12:02 The pipe is full
5.6、使用select+time实现超时写入或读取
// 读取超时
func readWithSelectTime(ch chan int) (int, error) {
timeout := time.NewTimer(time.Microsecond * 500)
select {
case x := <-ch:
return x, nil
case <-timeout.C:
return 0, errors.New("read timeout")
}
}
// 写入超时
func writeWithSelectTime(ch chan int) error {
timeout := time.NewTimer(time.Microsecond * 500)
select {
case ch <- 1:
return nil
case <-timeout.C:
return errors.New("write time out")
}
}
6、什么情况会引发panic?
对于一个已初始化,但未关闭的通道来说,收发操作一定不会引发panic;但是通道一旦关闭,再对它进行“发送”操作,就会引发panic。 试图关闭一个已经关闭了通道,也会引发panic。
关闭一个nil的通道
func main() {
var data chan int
close(data)
}
// output
panic: close of nil channel
关闭通道再进行“写入”操作
func main() {
data := make(chan int, 1)
close(data)
log.Println("关闭通道")
data <- 1
}
// output
2020/08/06 20:28:33 关闭通道
panic: send on closed channel
7、应用场景
7.1、超时
time.After会在另一线程经过时间段d后向返回值发送当时的时间
// withChannelTime
// 通过channel进行超时控制
func withChannelTime() {
done := make(chan int, 1)
// 协程写入
go func() {
time.Sleep(2 * time.Second)
done <- 1
}()
for {
select {
case work := <-done:
log.Println("sucess", work)
case <-time.After(1 * time.Second):
log.Println("timeout")
}
}
}
// output
$ go run main.go
2020/08/06 09:47:35 timeout
2020/08/06 09:47:36 sucess 1
2020/08/06 09:47:37 timeout
2020/08/06 09:47:38 timeout
7.2、取最快的结果
func main() {
data := make(chan string, 5)
for i := 0; i < 5; i++ {
go func(num int) {
data <- task(strconv.Itoa(num))
}(i)
}
log.Println("最快的结果", <-data)
}
func task(link string) string {
randNumber := rand.Intn(1000)
time.Sleep(time.Duration(randNumber) * time.Millisecond)
log.Println(link, randNumber)
return link
}
// output
2020/08/06 20:55:12 2 59
2020/08/06 20:55:12 最快的结果 2
7.3、协程之间的通知
启动两个线程, 一个输出 1,3,5,7…99, 另一个输出 2,4,6,8…100 最后 STDOUT 中按序输出 1,2,3,4,5…100?
// withChannel
// 使用管道方式进行通知
func withChannel() {
var wg sync.WaitGroup
var signal = make(chan struct{}, 1)
var signa2 = make(chan struct{}, 1)
wg.Add(2)
// 输出基数
go func() {
defer func() {
wg.Done()
}()
for i := 1; i <= 100; i += 2 {
if i != 1 {
// 读取数据,没有数据时阻塞
<-signal
}
// <-signal
log.Println("goroutine1->", i)
signa2 <- struct{}{}
}
}()
// 输出偶数
go func() {
defer func() {
wg.Done()
}()
for i := 2; i <= 100; i += 2 {
<-signa2
log.Println("goroutine2->", i)
signal <- struct{}{}
}
}()
wg.Wait()
}
7.4、多个协程同步响应
func main() {
data := make(chan struct{})
for i := 0; i < 5; i++ {
go task(data)
}
close(data)
time.Sleep(1 * time.Second)
}
func task(ch <-chan struct{}) {
<-ch
log.Println("接收")
}
// output
2020/08/06 22:07:28 接收
2020/08/06 22:07:28 接收
2020/08/06 22:07:28 接收
2020/08/06 22:07:28 接收
2020/08/06 22:07:28 接收
限制最大并发数
通过管道数量限制最大的并发数
func main() {
// 限制最大并发数
data := make(chan struct{}, 2)
for i := 0; i < 5; i++ {
data <- struct{}{}
go task()
<-data
}
time.Sleep(2 * time.Second)
}
func task() {
log.Println("任务")
}
2020/08/07 09:51:07 任务
2020/08/07 09:51:07 任务
2020/08/07 09:51:07 任务
2020/08/07 09:51:07 任务
2020/08/07 09:51:07 任务
注意事项
- 管道如果未关闭,在读取超时则会引发deadlock异常
- 管道如果关闭进行写入数据会发panic
- 当管道没有数据时候在行读取活读取到默认值
- 元素值从外界进入通道会被“复制”(具体描述,进入通道的并不是接收操作符右边的元素值,而是它的副本)
- 接收操作是可以感知通道的关闭的并能够安全退出
问题
如果在select语句中发现某个通道已经关闭,那么应该如何屏蔽调他所在的分支?【设置nil】
func main() {
work := asChan(1, 2)
for {
select {
case rs, ok := <-work:
if !ok {
work = nil
//break
}
log.Println(rs)
time.Sleep(2 * time.Second)
default:
log.Println("无选择")
time.Sleep(2 * time.Second)
}
}
}
func asChan(vs ...int) chan int {
ch := make(chan int)
go func() {
for _, v := range vs {
ch <- v
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
}
close(ch)
}()
return ch
}
// output
2020/08/06 11:23:04 无选择
2020/08/06 11:23:06 1
2020/08/06 11:23:08 2
2020/08/06 11:23:10 0
2020/08/06 11:23:12 无选择
在select语句与for语句联用是,怎样直接退出外层for语句?
答:将条件写在for循环中
通道的长度代表着什么?它在什么时候与通道的容量相同?
答:长度代表通道当前包含的元素个数,容量就是初始化设置的值