相比Erlang,go并未实现严格的并发安全。
允许全局变量、指针、引用类型这些非安全内存共享操作,就需要开发人员自行维护数据一致和完整性。
Go鼓励使用CSP通道,以通信来代替内存共享,实现并发安全。
作为CSP核心,通道(channel)是显式地,要求操作双方必须知道数据类型和具体通道,并不关心另一端操作者身份和数量。
可如果另一端未准备妥当,或消息未能及时处理时,会阻塞当前端。
相比起来,Actor是透明地,它不在乎数据类型及通道,只要知道接收者信箱即可。
默认就是异步方式,发送方消息是否被接收和处理并不关心。
从底层实现上来说,通道只是一个队列。
同步模式下,发送和接收双方配对,然后直接复制数据给对方。
如果配对失败,则置入等待队列,直到另一方出现后才被唤醒。
异步模式抢夺地则是数据缓冲槽。发送方要求有空槽可供写入,而接收方则要求有缓冲数据可读。
需求不符时,同样加入等待队列,直到有另一方写入数据或腾出空槽后被唤醒。

除传递消息(数据)外,通道还常被用作事件通知。

package main

import "fmt"

func main() {
done := make(chan struct{}) //消息传递通道
c := make(chan string) //数据传输通道 go func() {
s := <-c //接收消息
fmt.Println(s)
close(done) //关闭同道,作为结束通知
}() c <- "hi" //发送消息
<-done //阻塞,直到数据或管道关闭
}

  

同步模式必须有配对操作的goroutine出现,否则会一直阻塞。
而异步模式在缓冲区未满或数据未读前,不会阻塞。

package main

import "fmt"

func main() {
c := make(chan int, 3) //创建带有三个缓冲槽地异步通道 c <- 1 //缓冲区未满不会阻塞
c <- 2 fmt.Println(<-c) //缓冲区尚有数据,不会阻塞
fmt.Println(<-c)
}

  

多数时候,异步通道有助于提升性能,减少队伍阻塞。
缓冲区大小仅是内部属性,不属于类型组成部分。
另外通道变量本身就是指针,可用相等操作符判断是否为同一对象或nil。

package main

import (
"fmt"
"unsafe"
) func main() {
var a, b chan int = make(chan int, 3), make(chan int)
var c chan bool fmt.Println(a == b) //槽位不同
fmt.Println(c == nil) fmt.Printf("%p, %d\n", a, unsafe.Sizeof(a))
} /*
false
true
0xc000080080, 8
*/  

虽然可传递指针来避免数据复制,但须额外注意数据复制安全。

内置函数cap和len返回缓冲区大小和当前已缓存数量。
对于同步同步通道而言都返回0,据此可判断通道是同步还是异步。

package main

import "fmt"

func main() {
a, b := make(chan int), make(chan int, 3) b <- 1
b <- 2 fmt.Println("a:", len(a), cap(a))
fmt.Println("b:", len(b), cap(b))
} /*
a: 0 0 //给定槽位数量的就是异步
b: 2 3
*/

  

收发

除使用简单的发送和接收操作符外,还可以用ok-idom或range模式处理数据。

package main

import "fmt"

func main() {
done := make(chan struct{})
c := make(chan int) go func() {
defer close(done) for {
x, ok := <-c
if !ok {
return
}
fmt.Println(x)
}
// for x := range c {
// fmt.Println(x)
// } }() c <- 1
c <- 2
c <- 3
close(c)
<-done
}  

对于循环接收数据,range模式更简洁一些。

package main

import "fmt"

func main() {
done := make(chan struct{})
c := make(chan int) go func() {
defer close(done) for x := range c {
fmt.Println(x)
}
}() c <- 1
c <- 2
c <- 3
close(c)
<-done
}

及时用close函数关闭通道引发结束通知,否则可能会导致死锁。
通知可以是群体性的。也未必就是通知结束,可以是任何需要表达的事件。

package main

import (
"fmt"
"sync"
"time"
) func main() {
var wg sync.WaitGroup
ready := make(chan struct{}) for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done() fmt.Println(id, ":ready")
<-ready //接收消息
fmt.Println(id, ":running...")
}(i)
} time.Sleep(time.Second)
fmt.Println("ready? Go!") close(ready) //关闭通道,发出消息 wg.Wait()
} /*
0 :ready
1 :ready
2 :ready
ready? Go!
0 :running...
2 :running...
1 :running...
*/  

一次性事件用close效率更好,没有多余开销。连续或多样性事件,
可传递不同数据标志实现,还可以使用sync.Cloud实现单播或广播事件。

对于closed或nil通道,发送和接收操作都有相应规则。
向已关闭通道发送数据,引发panic。
从已关闭接收数据,返回已缓冲数据或零值。
无论收发,nil通道都会阻塞。

package main

import (
"fmt"
) func main() {
c := make(chan int, 3) c <- 10
c <- 20
close(c) for i := 0; i < cap(c)+1; i++ {
x, ok := <-c
fmt.Println(i, ":", ok, x)
}
} /*
0 : true 10
1 : true 20
2 : false 0
3 : false 0
*/  

注意,重复关闭或关闭nil通道都会引发panic错误。

单向

通道默认是双向的,并不区分发送和接收端。
但某些时候,我们可限制收发操作的方向类获得更严谨的操作逻辑。
尽管可用make创建单向通道,但那没有任何意义。
通常使用类型转换来获取单向通道,并分别赋予操作双方。

package main

import (
"fmt"
"sync"
) func main() {
var wg sync.WaitGroup
wg.Add(2) c := make(chan int)
var send chan<- int = c
var recv <-chan int = c go func() {
defer wg.Done() for x := range recv {
fmt.Println(x)
}
}() go func() {
defer wg.Done()
defer close(c) for i := 0; i < 3; i++ {
send <- i
}
}() wg.Wait()
} /*
0
1
2
*/ /*

  

不能再单向通道上做逆向操作。

func main() {
c := make(chan int, 2)
var send chan<- int = c
var recv <-chan int = c <-send
recv <- 1
}

  

close不能用于接收端。

func main() {
c := make(chan int, 2)
var recv <-chan int = c close(recv)
}

  

无法将单向通道重新转换回去。

func main() {
var a,b clan int
a := make(chan int, 2)
var send chan<- int = a
var recv <-chan int = a b = (chan int)(recv)
b = (chan int)(send)
}

  

选择

如果同时处理多个通道,可选用select语句。它会随机选择一个可用通道做收发操作。

package main

import (
"fmt"
"sync"
) func main() {
var wg sync.WaitGroup
wg.Add(2) a, b := make(chan int), make(chan int) //创建两个通道 go func() {
defer wg.Done() for {
var ( //定义三个变量
name string
x int
ok bool
) select { //随机选择一个通道接收消息
case x, ok = <-a:
name = "a"
case x, ok = <-b:
name = "b"
} if !ok { //如果任一通道关闭,则终止接收
return
} fmt.Println(name, x)
}
}() go func() {
defer wg.Done()
defer close(a)
defer close(b) for i := 0; i < 10; i++ {
select {
case a <- i: //随机发送10次消息
case b <- i * 10:
}
}
}()
wg.Wait()
} /*
a 0
b 10
b 20
a 3
a 4
a 5
a 6
b 70
a 8
b 90
*/

  

如果等全部通道消息处理结束,可将已完成通道设置为nil,这样它就会被阻塞,不再被select选中。

package main

import (
"fmt"
"sync"
) func main() {
var wg sync.WaitGroup
wg.Add(3) a, b := make(chan int), make(chan int) go func() {
defer wg.Done() for {
select {
case x, ok := <-a:
if !ok {
a = nil
break
}
fmt.Println("a", x)
case x, ok := <-b:
if !ok {
b = nil
break
}
fmt.Println("b", x)
}
if a == nil && b == nil {
return
} }
}() go func() {
defer wg.Done()
defer close(a) for i := 0; i < 3; i++ {
a <- i
}
}() go func() {
defer wg.Done()
defer close(b)
for i := 0; i < 5; i++ {
a <- i
} }()
wg.Wait() }

  

即便是同一通道,也会随机选择case执行。

package main

import (
"fmt"
"sync"
) func main() {
var wg sync.WaitGroup
wg.Add(2) c := make(chan int) go func() { //接收端
defer wg.Done() for {
var v int
var ok bool select { //随机选择case
case v, ok = <-c:
fmt.Println("a1:", v)
case v, ok = <-c:
fmt.Println("a2:", v)
}
if !ok {
return
}
}
}() go func() { //发送端
defer wg.Done()
defer close(c) for i := 0; i < 10; i++ { //随机选择case
select {
case c <- i:
case c <- i * 10:
}
}
}()
wg.Wait()
} /*
a1: 0
a2: 1
a2: 2
a2: 3
a2: 40
a1: 50
a1: 6
a2: 7
a2: 8
a2: 9
a2: 0
*/

  

当所有通道都不可用时,select会执行default语句。
如此可避开select阻塞,但须注意处理外层循环,以免陷入空耗。

package main

import (
"fmt"
"time"
) func main() {
done := make(chan struct{})
c := make(chan int) go func() {
defer close(done) for {
select {
case x, ok := <-c:
if !ok {
return
}
fmt.Println("data:", x)
default: //避免select阻塞
}
fmt.Println(time.Now())
time.Sleep(time.Second)
}
}() time.Sleep(time.Second * 5)
c <- 100
close(c) <-done
} /*
2018-12-03 06:52:57.1009398 +0800 CST m=+0.007029001
2018-12-03 06:52:58.1185419 +0800 CST m=+1.024631101
2018-12-03 06:52:59.1187182 +0800 CST m=+2.024807401
2018-12-03 06:53:00.1190807 +0800 CST m=+3.025169901
2018-12-03 06:53:01.1194511 +0800 CST m=+4.025540301
data: 100
2018-12-03 06:53:02.1198158 +0800 CST m=+5.025905001
*/

  

用default处理一些默认逻辑。

package main

import (
"fmt"
) func main() {
done := make(chan struct{}) data := []chan int{
make(chan int, 3),
} go func() {
defer close(done) for i := 0; i < 10; i++ {
select {
case data[len(data)-1] <- i:
default:
data = append(data, make(chan int, 3))
}
}
}() <-done for i := 0; i < len(data); i++ {
c := data[i]
close(c)
for x := range c {
fmt.Println(x)
}
}
}

通常使用工厂方法将goroutine和通道绑定。

package main

import (
"fmt"
"sync"
) type receiver struct {
sync.WaitGroup
data chan int
} func newReceiver() *receiver {
r := &receiver{
data: make(chan int),
} r.Add(1)
go func() {
defer r.Done()
for x := range r.data {
fmt.Println("recv:,", x)
}
}()
return r
} func main() {
r := newReceiver()
r.data <- 1
r.data <- 2 close(r.data)
r.Wait()
} /*
recv:, 1
recv:, 2
*/

  

鉴于通道本身就是一个并发安全的队列,可用作ID generator、Pool等用途。

package main

import (

)

type pool chan []byte

func newPool(cap int) pool {
return make(chan []byte, cap)
} func (p pool) get() []byte {
var v []byte select {
case v = <-p: //返回
default: //返回失败,新建
v = make([]byte, 10)
} return v
} func (p pool) put(b []byte) {
select {
case p <- b: //放回
default: //放回失败,放弃
}
}

  

用通道实现信号量。

package main

import (
"fmt"
"runtime"
"sync"
"time"
) func main() {
runtime.GOMAXPROCS(4)
var wg sync.WaitGroup sem := make(chan struct{}, 2) //最多允许两个并发同时执行
for i := 0; i < 5; i++ {
wg.Add(1) go func(id int) {
defer wg.Done() sem <- struct{}{}
defer func() { <-sem }() time.Sleep(time.Second * 2)
fmt.Println(id, time.Now())
}(i)
} wg.Wait()
} /*
4 2018-12-03 07:23:18.054693 +0800 CST m=+2.004868201
0 2018-12-03 07:23:18.054693 +0800 CST m=+2.004868201
1 2018-12-03 07:23:20.0942525 +0800 CST m=+4.044427701
3 2018-12-03 07:23:20.0942525 +0800 CST m=+4.044427701
2 2018-12-03 07:23:22.0948917 +0800 CST m=+6.045066901
*/

  

go——通道的更多相关文章

  1. Paypal开发中遇到请求被中止: 未能创建 SSL/TLS 安全通道及解决方案

    最近在基于ASP.NET上开发了Paypal支付平台,在ASP.NET开发的过程中没有遇到这个问题,但是引用到MVC开发模式中的时候就出现了"未能创建 SSL/TLS 安全通道及解决方案&q ...

  2. JAVA NIO Socket通道

      DatagramChannel和SocketChannel都实现定义读写功能,ServerSocketChannel不实现,只负责监听传入的连接,并建立新的SocketChannel,本身不传输数 ...

  3. 学习 opencv---(4) 分离颜色通道 && 多通道混合

    上篇文章中我们讲到了使用addWeighted函数进行图像混合操作,以及将ROI和addWeighted函数结合起来使用,对指定区域进行图像混合操作. 而为了更好地观察一些图像材料的特征,有时需要对R ...

  4. 关于QImage提取单色通道方法(vector)

    转载请标明处: 作者:微微苏荷 本文地址:关于QImage提取单色通道方法(vector) 近日,用QT和mxnet结合做一个图像识别的demo.遇到需要把图片从QImage转为vector单色分离的 ...

  5. 基于暗通道优先算法的去雾应用(Matlab/C++)

    基于暗通道优先的单幅图像去雾算法(Matlab/C++) 算法原理:             参见论文:Single Image Haze Removal Using Dark Channel Pri ...

  6. Java NIO4:Socket通道

    Socket通道 上文讲述了通道.文件通道,这篇文章来讲述一下Socket通道,Socket通道与文件通道有着不一样的特征,分三点说: 1.NIO的Socket通道类可以运行于非阻塞模式并且是可选择的 ...

  7. Java NIO3:通道和文件通道

    通道是什么 通道式(Channel)是java.nio的第二个主要创新.通道既不是一个扩展也不是一项增强,而是全新的.极好的Java I/O示例,提供与I/O服务的直接连接.Channel用于在字节缓 ...

  8. IO通道

    本文原创,转载需标明原处. 通道,主要负责传输数据,相当于流,但流只能是输入或输出类型中的其一,而通道则可以兼并二者. 通道的基类是:Channel boolean isOpen() void clo ...

  9. MQ通道配置

    转自:http://www.cnblogs.com/me115/p/3471788.html MQ通道配置 通道是用来连接两个队列管理器的: 在单个队列管理器内读写消息不需要建立通道:但在一个队列管理 ...

  10. 什么是Alpha通道?

    图像处理(Alpha通道,RGB,...)祁连山(Adobe 系列教程)****的UI课程 一个也许很傻的问题,在图像处理中alpha到底是什么?  Alpha通道是计算机图形学中的术语,指的是特别的 ...

随机推荐

  1. CC1101 433无线模块,STM8串口透传

    CC1101 433无线模块,STM8串口透传   原理图:http://download.csdn.net/detail/cp1300/7496509 下面是STM8程序 CC1101.C /*** ...

  2. Linux上安装Nginx及常用命令

    一.Linux安装软件常用方法 1.rpm(或pkg)安装,类似于Windows安装程序,是预编译好的程序. 1)使用的是通用参数编译,配置参数不是最佳 2)可控制性不强,比如对程序特定组件的定制性安 ...

  3. Prerender Application Level Middleware - ASP.NET Core Middleware

    In the previous post Use Prerender to improve AngularJS SEO, I have explained different solutions at ...

  4. 如何在调试Window App时,触发 Suspending ,Resuming 等事件

    Visual Studio prevents Windows from suspending an app that is attached to the debugger. This is to a ...

  5. 常见sql 写法总结

    关于如何获取1对多数据中最大条数据的写法 例子: LEFT JOIN ( SELECT * FROM table AS n1 WHERE n1.ID IN ( SELECT MAX(id) FROM ...

  6. Tensorflow之计算tensor平均值

    https://www.tensorflow.org/versions/r0.12/api_docs/python/math_ops.html#reduce_mean tf.reduce_mean(i ...

  7. jsp有哪些内置对象?作用分别是什么?(至少三个)

    jsp有哪些内置对象?作用分别是什么?(至少三个) 解答: 1)request表示HttpServletRequest对象.它包含了有关浏览器请求的信息,并且提供了几个用于获取cookie, head ...

  8. Ajax.ActionLink用法

    必须要引用的JS库: <script type="text/javascript" src="@Url.StaticFile("/Assets/Conte ...

  9. Hibernate Tools插件的使用

            Hibernate Tools是由JBoss推出的一个Eclipse综合开发工具插件,该插件可以简化ORM框架Hibernate,以及JBoss Seam,EJB3等的开发工作.Hib ...

  10. The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Cha

    The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Cha ...