go——通道

时间:2022-05-08 06:02:34

相比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开发中遇到请求被中止&colon; 未能创建 SSL&sol;TLS 安全通道及解决方案

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

  2. JAVA NIO Socket通道

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

  3. 学习 opencv---&lpar;4&rpar; 分离颜色通道 &amp&semi;&amp&semi; 多通道混合

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

  4. 关于QImage提取单色通道方法(vector&rpar;

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

  5. 基于暗通道优先算法的去雾应用&lpar;Matlab&sol;C&plus;&plus;&rpar;

    基于暗通道优先的单幅图像去雾算法(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. iOS-KVC&sol;KVO的理解

    1.KVC:Key-Value Coding,直译是:键值编码.简单来讲,就是给属性设置值的:复杂来讲,根据网上的说法,KVC运用了一个isa-swizzling技术.isa-swizzling就是类 ...

  2. DataGuard相同SID物理Standby搭建

    Oracle Data Guard 是针对企业数据库的最有效和最全面的数据可用性.数据保护和灾难恢复解决方案.它提供管理.监视和自动化软件基础架构来创建和维护一个或多个同步备用数据库,从而保护数据不受 ...

  3. ubuntu 14&period;04 允许root 登录

    在/etc/lightdm/lightdm.conf里添加一下两句: greeter-show-manual-login=true allow-guest=false

  4. 前端模块化:RequireJS&lpar;转&rpar;

    前言 前端模块化能解决什么问题? 模块的版本管理 提高可维护性 -- 通过模块化,可以让每个文件职责单一,非常有利于代码的维护 按需加载 -- 提高显示效率 更好的依赖处理 -- 传统的开发模式,如果 ...

  5. 解决ActionBar中的item不显示在ActionBar的问题

    今天在用ActionBar,需要增加一个菜单选项,按教程在/res/menu下对应的布局文件中添加了一个item,但是它却是显示在overflow中,而不是直接显示在ActionBar当中的.我的布局 ...

  6. nginx优化php-fpm优化 压力测试达到每分150万访问量webbench网站压力

    webbench最多可以模拟3万个并发连接去测试网站的负载能力,个人感觉要比Apache自带的ab压力测试工具好,安装使用也特别方便. 1.适用系统:Linux 2.编译安装:引用wget http: ...

  7. BZOJ&period;2199&period;&lbrack;USACO2011 Jan&rsqb;奶牛议会&lpar;2-SAT&rpar;

    题目链接 建边不说了.对于议案'?'的输出用拓扑不好判断,直接对每个议案的结果DFS,看是否会出现矛盾 Tarjan也用不到 //964kb 76ms #include <cstdio> ...

  8. 世界各个地区WIFI 2&period;4G及5G信道划分表(附无线通信频率分配表)

    参考:https://blog.csdn.net/dxpqxb/article/details/80969760 目前主流的无线WIFI网络设备802.11a/b/g/n/ac: 传统 802.11 ...

  9. &lbrack;uart&rsqb;linux串口的阻塞非阻塞切换

    比如写的时候设置为阻塞,读的时候设置为非阻塞,就需要下面的切换方式 1.获取文件的flags,即open函数的第二个参数: flags = fcntl(fd,F_GETFL,0); 2.设置文件的fl ...

  10. thymeleaf的常见问题汇总

    thymeleaf的常见问题汇总 1.thymeleaf th:href 多个参数传递格式 th:href="@{/Controller/update(param1=1,param2=${p ...