首先我们来看线程,在golang里面也叫goroutine

Go 语言 select 语句

select是Go中的一个控制结构,类似于用于通信的switch语句。每个case必须是一个通信操作,要么是发送要么是接收。

select随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。一个默认的子句应该总是可运行的。

语法

Go 编程语言中 select 语句的语法如下:

select {case communication clause  :statement(s);      case communication clause  :statement(s); /* 你可以定义任意数量的 case */default : /* 可选 */statement(s);
}

以下描述了 select 语句的语法:

  • 每个case都必须是一个通信
  • 所有channel表达式都会被求值
  • 所有被发送的表达式都会被求值
  • 如果任意某个通信可以进行,它就执行;其他被忽略。
  • 如果有多个case都可以运行,Select会随机公平地选出一个执行。其他不会执行。 
    否则:

    1. 如果有default子句,则执行该语句。
    2. 如果没有default字句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。
[plain] view plaincopy
  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. var chan_test chan interface{}    //双项通道
  7. var read_test <-chan interface{}  //单项通道 只支持 读
  8. var write_test chan<- interface{} //单项通道 只支持 写
  9. read_test <- "xiao"               //会报错。因为这个通道 只是单项通道只支持读 不支持写
  10. <-write_test                      //会报错。因为这个通道 只是单项通道只支持写 不支持读
  11. fmt.Println(chan_test)
  12. fmt.Println(read_test)
  13. fmt.Println(write_test)
  14. }

我们需要了解一下并发与并行。golang的线程是一种并发机制,而不是并行。它们之间的区别大家可以上网搜一下,网上有很多的介绍。

下面我们先来看一个例子吧

import("fmt"
)funcmain(){go fmt.Println("1")fmt.Println("2")
}

在golang里面,使用go这个关键字,后面再跟上一个函数就可以创建一个线程。后面的这个函数可以是已经写好的函数,也可以是一个匿名函数

funcmain(){var i=3go func(a int) {fmt.Println(a)fmt.Println("1")}(i)fmt.Println("2")}

上面的代码就创建了一个匿名函数,并且还传入了一个参数i,下面括号里的i是实参,a是形参。

那么上面的代码能按照我们预想的打印1、2、3吗?告诉你们吧,不能,程序只能打印出2。下面我把正确的代码贴出来吧

import("fmt""time"
)
funcmain(){var i = 3go func(a int) {fmt.Println(a)fmt.Println("1")}(i)fmt.Println("2")time.Sleep(1 * time.Second)
}

我只是在最后加了一行让主线程休眠一秒的代码,程序就会依

次打印出2、3、1。 
那为什么会这样呢?因为程序会优先执行主线程,主线程执行完成后,程序会立即退出,没有多余的时间去执行子线程。如果在程序的最后让主线程休眠1秒钟,那程序就会有足够的时间去执行子线程。

线程先讲到这里,下面我们来看看通道吧。

通道又叫channel,顾名思义,channel的作用就是在多线程之间传递数据的。

创建无缓冲channel

chreadandwrite :=make(chan int)

chonlyread := make(<-chan int) //创建只读channel 
chonlywrite := make(chan<- int) //创建只写channel 
下面我们来看一个例子:

    ch :=make(chan int)     ch <- 1go func() {<-chfmt.Println("1")}()fmt.Println("2")  

这段代码执行时会出现一个错误:fatal error: all goroutines are asleep - deadlock!

这个错误的意思是说线程陷入了死锁,程序无法继续往下执行。那么造成这种错误的原因是什么呢?

我们创建了一个无缓冲的channel,然后给这个channel赋值了,程序就是在赋值完成后陷入了死锁。因为我们的channel是无缓冲的,即同步的,赋值完成后来不及读取channel,程序就已经阻塞了。这里介绍一个非常重要的概念:channel的机制是先进先出,如果你给channel赋值了,那么必须要读取它的值,不然就会造成阻塞,当然这个只对无缓冲的channel有效。对于有缓冲的channel,发送方会一直阻塞直到数据被拷贝到缓冲区;如果缓冲区已满,则发送方只能在接收方取走数据后才能从阻塞状态恢复。

对于上面的例子有两种解决方案:

1、给channel增加缓冲区,然后在程序的最后让主线程休眠一秒,代码如下:

    ch :=make(chan int,1)ch <- 1go func() {v := <-chfmt.Println(v)}()time.Sleep(1 * time.Second)fmt.Println("2")

这样的话程序就会依次打印出1、2

2、把ch<-1这一行代码放到子线程代码的后面,代码如下:

    ch :=make(chan int)go func() {v := <-chfmt.Println(v)}()ch <- 1fmt.Println("2")

这里就不用让主线程休眠了,因为channel在主线程中被赋值后,主线程就会阻塞,直到channel的值在子线程中被取出。

最后我们看一个生产者和消费者的例子:

import ("fmt""time"
)
func produce(p chan<- int) {for i := 0; i < 10; i++ {p <- ifmt.Println("send:", i)}
}
func consumer(c <-chan int) {for i := 0; i < 10; i++ {v := <-cfmt.Println("receive:", v)}
}
func main() {ch := make(chan int)go produce(ch)go consumer(ch)time.Sleep(1 * time.Second)
}

在这段代码中,因为channel是没有缓冲的,所以当生产者给channel赋值后,生产者这个线程会阻塞,直到消费者线程将channel中的数据取出。消费者第一次将数据取出后,进行下一次循环时,消费者的线程也会阻塞,因为生产者还没有将数据存入,这时程序会去执行生产者的线程。程序就这样在消费者和生产者两个线程间不断切换,直到循环结束。

下面我们再看一个带缓冲的例子:

import ("fmt""time"
)
func produce(p chan<- int) {for i := 0; i < 10; i++ {p <- ifmt.Println("send:", i)}
}
func consumer(c <-chan int) {for i := 0; i < 10; i++ {v := <-cfmt.Println("receive:", v)}
}
func main() {ch := make(chan int, 10)go produce(ch)go consumer(ch)time.Sleep(1 * time.Second)
}

在这个程序中,缓冲区可以存储10个int类型的整数,在执行生产者线程的时候,线程就不会阻塞,一次性将10个整数存入channel,在读取的时候,也是一次性读取。

channels 是 goroutines之间通信的工具, 可以理解为管道, 虽然go也提供共享变量的方式, 但是更加推荐使用channel

[plain] view plaincopy
  1. func TestChan(t *testing.T) {
  2. c := make(chan int)
  3. go func() {
  4. c <- 48
  5. }()
  6. fmt.Println(<- c)
  7. // 保持持续运行
  8. holdRun()
  9. }
  10. func holdRun() {
  11. time.Sleep(1 * time.Hour)
  12. }

c := make(chan int)    声明一个  传输整形 的unbuffer  chan,(接收消息和发送消息者将会阻塞,直到channel ”可用“)

<-  操作符用来接受和发送消息   chan <- 48    发送“48“ 进入管道,   <-chan   接收消息

如果: c: = make(chan int, 10)   声明一个  传输整形 的buffer  chan, 容量为10, 接收消息将可以立即返回除非channel里面没有消息, 发送者返回除非容量满

[plain] view plaincopy
  1. func TestDeadLock(t *testing.T) {
  2. c := make(chan int)
  3. c <- 42
  4. val := <-c
  5. fmt.Println(val)
  6. }
  7. func TestDeadLock1(t *testing.T) {
  8. c := make(chan int)
  9. //c := make(chan int, 0)
  10. go func() {
  11. c <- 48
  12. }()
  13. val := <-c
  14. fmt.Println(val)
  15. }
  16. func TestDeadLock2(t *testing.T) {
  17. c := make(chan int, 1)
  18. c <- 42
  19. val := <-c
  20. fmt.Println(val)
  21. }

对于方法, TestDeadLock  将: fatal error: all goroutines are asleep - deadlock!  因为c <- 42 将会一直阻塞,直到出现消费者, 无容量的chan是同步, 正确的写法是 TestDeadLock1 这样不会死锁, 或者  TestDeadLock2 也不会死锁

[plain] view plaincopy
  1. func TestChan(t *testing.T) {
  2. c := make(chan int,  10)
  3. go func() {
  4. c <- 48
  5. c <- 96
  6. time.Sleep(2 * time.Second)
  7. c <- 200
  8. }()
  9. time.Sleep(1 * time.Second)
  10. for v := range c {
  11. fmt.Println(v)
  12. }
  13. // 保持持续运行
  14. holdRun()
  15. }

chan  可以配合 range 使用, 相当于每次foreach 每次去取一次

[plain] view plaincopy
  1. func TestDChan(t *testing.T) {
  2. c := make(chan int)
  3. go f1(c)
  4. holdRun()
  5. }
  6. func f1(c chan <- int) {
  7. c <- 0
  8. <- c
  9. }

f1的参数类型是  chan <- int   表明 这个chan单向的, 只能用来接收。 f1函数编译错误: invalid operation: <-c (receive from send-only type chan<- int)

相对应的发送的chan  : c <-chan string

select 关键字可以和  chan使用, 类似与switch

[plain] view plaincopy
  1. func TestSelect(t *testing.T) {
  2. c1 := make(chan int)
  3. c2 := make(chan int, 10)
  4. c3 := make(chan int, 20)
  5. go func(c1, c2, c3 chan<- int) {
  6. for {
  7. time.Sleep(1 * time.Second)
  8. c1 <- 1
  9. time.Sleep(3 * time.Second)
  10. c2 <- 2
  11. time.Sleep(1 * time.Second)
  12. c3 <- 3
  13. }
  14. }(c1, c2, c3)
  15. for {
  16. select {
  17. case int1 := <-c1:
  18. fmt.Println("c1 value :", int1)
  19. case int2 := <-c2:
  20. fmt.Println("c2 value :", int2)
  21. case int3 := <-c3:
  22. fmt.Println("c3 vaule :", int3)
  23. case <-time.After(2 * time.Second):
  24. fmt.Println("timeount")
  25. }
  26. }
  27. }

select 将阻塞直到有一个chan ready或者 超时,即 time.After

[plain] view plaincopy
  1. select {
  2. case int1 := <-c1:
  3. fmt.Println("c1 value :", int1)
  4. case int2 := <-c2:
  5. fmt.Println("c2 value :", int2)
  6. case int3 := <-c3:
  7. fmt.Println("c3 vaule :", int3)
  8. default:
  9. fmt.Println("nothing ready")
  10. }

select 将不会阻塞, 直接执行 default

1、通道关闭时间: 
一般紧跟在往通道输入最后一个数据之后。

    jobs := make(chan int, 5)for i := 1; i < 4; i++ {jobs <- ifmt.Println("sent job", i)//      if i == 3 {//          close(jobs)//      }}close(jobs)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2、读取关闭的无缓存通道: 
读取关闭后的无缓存通道,不管通道中是否有数据,返回值都为0和false。

    done := make(chan int)go func() {done <- 1}()close(done)for i := 1; i <= 3; i++ {t, ok := <-donefmt.Println(i, ":", t, ok)}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

运行结果: 
1:0 false 
2:0 false 
3:0 false

3、读取关闭的有缓存通道: 
读取关闭后的有缓存通道,将缓存数据读取完后,再读取返回值为0和false。

    done := make(chan int, 1)go func() {done <- 1}()close(done)for i := 1; i <= 3; i++ {t, ok := <-donefmt.Println(i, ":", t, ok)}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

运行结果: 
1:1 true 
2:0 false 
3:0 false

4、range遍历通道: 
通道写完后,必须关闭通道,否则range遍历会出现死锁。

附 :

time.Afer 实现:

[plain] view plaincopy
  1. func After(d Duration) <-chan Time {
  2. return NewTimer(d).C
  3. }
[plain] view plaincopy
  1. func NewTimer(d Duration) *Timer {
  2. c := make(chan Time, 1)
  3. t := &Timer{
  4. C: c,
  5. r: runtimeTimer{
  6. when: when(d),
  7. f:    sendTime,
  8. arg:  c,
  9. },
  10. }
  11. startTimer(&t.r)
  12. return t
  13. }

NewTimer 返回一个 timer (timer是一个一次性时间,d 时间后 发送当前时间给  C) , 由于C在此之前会一直阻塞。从而达到超时的效果

1.广播

当一个通道关闭时, 所有此通道的读取都会退出阻塞. 利用此特性可以实现广播功能

package mainimport ("fmt""time"
)func main() {c := make(chan bool)for i := 0; i < 5; i++ {go func(n int) {<-c //读取到数据或通道关闭时会退出阻塞fmt.Println("收到通知:", n)}(i)}fmt.Println("广播通知")close(c) //关闭通道, 广播通知time.Sleep(time.Second * 1) //等待其它协程处理}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

2.同时读取多个通道

有时需要监视多个通道, 这个时候可以使用select

package mainimport ("fmt""time"
)func main() {c1 := make(chan int)c2 := make(chan int)go func() {var n intselect {case n = <-c1:case n = <-c2:}fmt.Println("数据:", n)}()fmt.Println("写入")c1 <- 1//c2 <- 2close(c1)close(c2)time.Sleep(time.Second * 1) //等待其它协程处理}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

3.超时

channel本身无法设置超时, 可以使用select和定时器实现超时功能

package mainimport ("fmt""time"
)func main() {c := make(chan int, 1)go func() {timeout := falsevar n intselect {case n = <-c:case <-time.After(time.Second * 1):timeout = true}if timeout {fmt.Println("超时")} else {fmt.Println("读取到数据:", n)}}()time.Sleep(time.Second * 2)c <- 2time.Sleep(time.Second * 3) //等待其它协程处理}

Go 通道 Chan 详解相关推荐

  1. 【转】Golang 关于通道 Chan 详解

    原文: http://blog.csdn.net/netdxy/article/details/54564436 在用 chan 类型时,发生死锁的错误,表面上看不出什么问题 ------------ ...

  2. Java NIO学习篇之通道FileChannel详解

    定义: FileChannel是Java NIO对应于磁盘等存储设备文件操作的通道. 常用API详解: 获取FileChannel的API /** * 打开一个与文件的连接通道,用于进行文件操作. * ...

  3. mysql ssh通道_详解如何通过SSH通道来访问MySQL

    原标题:详解如何通过SSH通道来访问MySQL 许多时候当要使用Mysql时,会遇到如下情况: 1. 信息比较重要,希望通信被加密. 2. 一些端口,比如3306端口,被路由器禁用. 对第一个问题的一 ...

  4. c语言给bmp图片加滤镜,关于BMP位图透明通道的详解制作教程, 教你输出透明的BMP位图...

    我是sjmhiex啊月谢谢大家的支持  百度贴吧:sjmhiex吧 QQ群:243153684 BMP支持透明比较常见的方法有两种: 一种是32位图,直接就可以是透明的,还可以是半透明效果,一般都是用 ...

  5. Java NIO学习篇之通道Channel详解

    定义: Channel:通道,运输的介质,可以大致比喻成铁路的铁轨,连接着两个车站,而channel用于打开与IO设备的连接,比如磁盘,套接字等. 通道使用完需要关闭. 与传统IO的Stream比较: ...

  6. CPU的快速互联通道(QPI)详解

    翻译自:http://www.hardwaresecrets.com/article/Everything-You-Need-to-Know-About-The-QuickPath-Interconn ...

  7. SU插件情报局 | Suchannels (一键通道)详解

    作者:活力网Andrew 同学们大扎好! 活力网 SU插件情报局又来了! 这一期我们分享一个 特别简单并且实用的小插件 Suchannels (一键通道) 插件作者是Renderiza(RND) 当我 ...

  8. Android 阿里云移动推送厂商辅助通道集成详解

    厂商通道配置 1.添加额外依赖项 implementation 'com.aliyun.ams:alicloud-android-push:3.1.9.1'implementation 'com.al ...

  9. [Go语言入门] 14 Go语言goroutine和通道详解

    文章目录 14 Go语言goroutine和通道详解 14.1 goroutine 14.2 通道(channel) 声明通道变量 创建通道 通道操作 14.3 管道 14.4 单向通道 14.5 通 ...

  10. python将图像转换为8位单通道_【图像处理】OpenCV系列三十五--- equalizeHist函数详解...

    上一节,我们学习了如何对两个直方图进行比较,看两幅图像的相似度是多少,经过上节的学习,相信大家对compareHist函数已经有了一个清晰的理解,本届呢,我们学习如何对一幅图像进行均衡化! 1.函数原 ...

最新文章

  1. java运行环境jdk的安装和环境变量的配置教程
  2. 重做日志和控制文件的多路复用
  3. [MVC 4] ActionResult 使用示例
  4. 阿里云日志服务(ELK)
  5. 【Python 标准库学习】容器数据类型库 — collections
  6. 【Python】爬虫原理
  7. 案例:实现文件下载并能正确显示中文文件名
  8. 【BZOJ4837】LRU算法 [模拟]
  9. 使用Yii2中dropdownlist实现地区三级联动的例子
  10. matlab中cell(17),Matlab中uigetfile( ) iscell() 函数的使用
  11. 存储容量与地址空间的计算
  12. 原则与思维模型--《思维模型》2
  13. 0xE06D7363: Microsoft C++ Exception.
  14. 对于大学工作规划和面试前准备
  15. 域策略设置计算机睡眠,win10专业版中如何使用组策略跳过从睡眠中恢复的密码提示...
  16. 1297: 小数化分数
  17. Android开发中所需颜色的RGB值
  18. 如何选择合适的统计学方法
  19. DataBricks从开源到商业化踩过的坑
  20. 多渠道归因分析(Attribution):传统归因(一)

热门文章

  1. 为什么中国人少有人得到诺贝尔奖呢?
  2. 校园歌手大赛10分c语言,校园歌手大赛评分细则
  3. ASP.NET删除服务器端文件,asp.net删除服务器上的文件
  4. Docker部署Nebula Graph2.0和Studio
  5. uni-app和H5页面使用支付宝登陆问题解决
  6. 全国青少年软件编程等级考试标准Python(1-6级)
  7. 地铁机房环境监控系统解决方案!
  8. 音响无声或声音不正常的原因有哪些?
  9. 【HBZ】生产环境下如何解决CPU飙高 与排查CPU飙高问题 与如何解决内存泄漏
  10. 语义化版本号 Sematic Versioning