GO中的channel使用小结
go关键字可以用来开启一个goroutine(协程))进行任务处理,而多个任务之间如果需要通信,就需要用到channel了。
func testSimple(){
intChan := make(chan int)
go func() {
intChan <- 1
}()
value := <- intChan
fmt.Println("value : ", value)
}
上面这个简单的例子就是新开启的goroutine向intChan发送了一个1的值,那么在主线程的intChan就会收到这个值的信息。
channel类型:无缓冲和缓冲类型
channel有两种形式的,一种是无缓冲的,一个线程向这个channel发送了消息后,会阻塞当前的这个线程,知道其他线程去接收这个channel的消息。无缓冲的形式如下:
intChan := make(chan int)
带缓冲的channel,是可以指定缓冲的消息数量,当消息数量小于指定值时,不会出现阻塞,超过之后才会阻塞,需要等待其他线程去接收channel处理,带缓冲的形式如下:
//3为缓冲数量
intChan := make(chan int, )
传输struct结构数据
channel可以传输基本类型的数据如int, string,同时也可以传输struct数据
type Person struct {
Name string
Age uint8
Address Addr
}
type Addr struct {
city string
district string
}
/*
测试channel传输复杂的Struct数据
*/
func testTranslateStruct() {
personChan := make(chan Person, )
person := Person{"xiaoming", , Addr{"shenzhen", "longgang"}}
personChan <- person
person.Address = Addr{"guangzhou", "huadu"}
fmt.Printf("src person : %+v \n", person)
newPerson := <-personChan
fmt.Printf("new person : %+v \n", newPerson)
}
这里可以看到可以通过channel传输自定义的Person对象,同时一端修改了数据,不影响另一端的数据,也就是说通过channel传递后的数据是独立的。
关闭channel
channel可以进行关闭,例如写的一段关闭了channel,那么读的一端读取时就可以检测读取失败
/*
测试关闭channel
*/
func testClose() {
ch := make(chan int, )
sign := make(chan int, ) go func() {
for i := ; i <= ; i++ {
ch <- i
time.Sleep(time.Second)
} close(ch) fmt.Println("the channel is closed") sign <- }() go func() {
for {
i, ok := <-ch
fmt.Printf("%d, %v \n", i, ok) if !ok {
break
} time.Sleep(time.Second * )
} sign <- }() <-sign
<-sign
}
合并多个channel的输出
可以将多个channel的数据合并到一个channel进行输出,形成一个消息队列
/**
将多个输入的channel进行合并成一个channel
*/
func testMergeInput() {
input1 := make(chan int)
input2 := make(chan int)
output := make(chan int) go func(in1, in2 <-chan int, out chan<- int) {
for {
select {
case v := <-in1:
out <- v
case v := <-in2:
out <- v
}
}
}(input1, input2, output) go func() {
for i := ; i < ; i++ {
input1 <- i
time.Sleep(time.Millisecond * )
}
}() go func() {
for i := ; i < ; i++ {
input2 <- i
time.Sleep(time.Millisecond * )
}
}() go func() {
for {
select {
case value := <-output:
fmt.Println("输出:", value)
}
}
}() time.Sleep(time.Second * )
fmt.Println("主线程退出")
}
通过channel实现退出的通知
定义一个用于退出的channel比如quit,不断执行任务的线程通过select监听quit的读取,当读取到quit中的消息时,退出当前的任务线程,这里是主线程通知任务线程退出。
/*
测试channel用于通知中断退出的问题
*/
func testQuit() {
g := make(chan int)
quit := make(chan bool) go func() {
for {
select {
case v := <-g:
fmt.Println(v)
case <-quit:
fmt.Println("B退出")
return
}
}
}() for i := 0; i < 3; i++ {
g <- i
}
quit <- true
fmt.Println("testAB退出")
}
生产者消费者问题
通过channel可以比较方便的实现生产者消费者模型,这里开启一个生产者线程,一个消费者线程,生产者线程往channel中发送消息,同时阻塞,消费者线程轮询获取channel中的消息,
进行处理,然后阻塞,这时生产者线程唤醒继续后面的逻辑,如此便形成了简单的生产者消费者模型。同时生产者在完成了所有的消息发送后,可以通过quit这个channel通知消费者线程退出,
而消费者线程退出时,通知主线程退出,整个程序完成退出。
/**
生产者消费者问题
*/
func testPCB() {
fmt.Println("test PCB") intchan := make(chan int)
quitChan := make(chan bool)
quitChan2 := make(chan bool) value := go func() {
for i := ; i < ; i++ { value = value +
intchan <- value fmt.Println("write finish, value ", value) time.Sleep(time.Second)
}
quitChan <- true
}()
go func() {
for {
select {
case v := <-intchan:
fmt.Println("read finish, value ", v)
case <-quitChan:
quitChan2 <- true
return
}
} }() <-quitChan2
fmt.Println("task is done ")
}
输出顺序问题
/*
这个结果输出是1,2, 也可能是2,1, 也可能是2,顺序是不一定的
*/
func testSequnse() {
ch := make(chan int) go func() {
v := <-ch
fmt.Println(v)
}()
ch <-
fmt.Println("")
}
上面这个输出结果是什么呢?运行一下会发现,可能是1,2,也可能是2,1, 也可能是2,顺序是不一定的,那么为什么会是这样的,我觉得因为这是两个不同的线程,
它们是独立运行的,当v := <-ch 执行之后,主线程和当前线程都是运行状态(非阻塞),先执行主线程还是新线程的输出就看cpu运行了,所以结果是不确定的。
channel的超时处理
通过time可以实现channel的超时处理,当一个channel读取超过一定时间没有消息到来时,就可以得到超时通知处理,防止一直阻塞当前线程
/*
检查channel读写超时,并做超时的处理
*/
func testTimeout() {
g := make(chan int)
quit := make(chan bool) go func() {
for {
select {
case v := <-g:
fmt.Println(v)
case <-time.After(time.Second * time.Duration()):
quit <- true
fmt.Println("超时,通知主线程退出")
return
}
}
}() for i := ; i < ; i++ {
g <- i
} <-quit
fmt.Println("收到退出通知,主线程退出")
}
channel的输入输出类型指定
channel可以在显示指定它是输入型还是输出型的,指定为输入型,则不能使用它输出消息,否则出错编译不通过,同理,输出型不能接受消息输入,
这样可以在编写代码时防止手误写错误输入输出类型而导致程序错误的问题。指定输入输出类型可以在方法参数时设定,那么它只在当前方法中会做输入输出限制,
可看下面实现。
/*
指定channel是输入还是输出型的,防止编写时写错误输入输出,指定了的话,可以在编译时期作错误的检查
*/
func testInAndOutChan() {
ch := make(chan int)
quit := make(chan bool) //输入型的chan是这种格式的:inChan chan<- int,如果换成输出型的,则编译时会报错
go func(inChan chan<- int) {
for i := ; i < ; i++ {
inChan <- i
time.Sleep(time.Millisecond * )
}
quit <- true
quit <- true
}(ch) go func(outChan <-chan int) {
for {
select {
case v := <-outChan:
fmt.Println("print out value : ", v)
case <-quit:
fmt.Println("收到退出通知,退出")
return
}
}
}(ch) <-quit
fmt.Println("收到退出通知,主线程退出")
}
channel实现并发数量控制
通过设置一个带缓冲数量的的channel来实现最大并发数量,最大并发数量即为缓冲数量,任务开始时想limit这个channel发送消息,
任务执行完成后从这个limit读取消息,这样就可以保证当并发数量达到limit的缓冲数量时,limit <- true 这里会发生阻塞,停止
创建新的线程,知道某个线程执行完成任务后,从limit读取数据,这样就能保证最大并发数量控制在缓冲数量。
/*
测试通过channel来控制最大并发数,来处理事件
*/
func testMaxNumControl() {
maxNum :=
limit := make(chan bool, maxNum)
quit := make(chan bool) for i:=; i<; i++{
fmt.Println("start worker : ", i) limit <- true go func(i int) {
fmt.Println("do worker start: ", i)
time.Sleep(time.Millisecond * )
fmt.Println("do worker finish: ", i) <- limit if i == {
fmt.Println("完成任务")
quit <- true
} }(i)
} <-quit
fmt.Println("收到退出通知,主程序退出")
}
监听中断信号的channel
可以创建一个signal信号的channel,同时通过signal.Notify来监听os.Interrupt这个中断信号,因此执行到<- quit时就会阻塞在这里,
直到收到了os.Interrupt这个中断信号,比如按Ctrl+C中断程序的时候,主程序就会退出了。当然还可以监听其他信号,例如os.Kill等。
/*
监听中断信号的channel
*/
func testSignal() {
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt) go func() {
time.Sleep(time.Second * ) number := ;
for{
number++
println("number : ", number)
time.Sleep(time.Second)
}
}() fmt.Println("按Ctrl+C可退出程序")
<- quit
fmt.Println("主程序退出") }
channel实现同步控制,生产者消费者模型
开启多个线程做赚钱和花钱的操作,共享读写remainMoney这个剩余金额变量,实现生产者消费者模型
//同步控制模型,生产者模型
var lockChan = make(chan int, )
var remainMoney =
func testSynchronize() {
quit := make(chan bool, ) go func() {
for i:=; i<; i++{
money := (rand.Intn() + ) *
go testSynchronize_expense(money) time.Sleep(time.Millisecond * time.Duration(rand.Intn()))
} quit <- true
}() go func() {
for i:=; i<; i++{
money := (rand.Intn() + ) *
go testSynchronize_gain(money) time.Sleep(time.Millisecond * time.Duration(rand.Intn()))
} quit <- true
}() <- quit
<- quit fmt.Println("主程序退出")
} func testSynchronize_expense(money int) {
lockChan <- if(remainMoney >= money){
srcRemainMoney := remainMoney
remainMoney -= money
fmt.Printf("原来有%d, 花了%d,剩余%d\n", srcRemainMoney, money, remainMoney)
}else{
fmt.Printf("想消费%d钱不够了, 只剩%d\n", money, remainMoney)
} <- lockChan
} func testSynchronize_gain(money int) {
lockChan <- srcRemainMoney := remainMoney
remainMoney += money
fmt.Printf("原来有%d, 赚了%d,剩余%d\n", srcRemainMoney, money, remainMoney) <- lockChan
}
---------------------
参考:https://blog.csdn.net/hesong1120/article/details/84326963
GO中的channel使用小结的更多相关文章
- Delphi中ClientDataSet的用法小结
Delphi中ClientDataSet的用法小结 TClientDataSet控件继承自TDataSet,其数据存储文件格式扩展名为 .cds,是基于文件型数据存储和操作的控件.该控件封装了对数据进 ...
- EntityFramework中几种操作小结
目前项目中使用到的EntityFramework中几种操作小结,先标记下.没有详细介绍,后续有空的话再补充一些并完善一下. 列中加入RowVersion时间戳 public class Product ...
- Flume-NG中的Channel与Transaction关系(原创)
在sink和source中(不管是内置还是自定义的),基本都有如下代码,这些代码在sink中的process方法中,而在source中自己不需要去写,在source中getChannelProcess ...
- 关于 C# 中接口的一些小结
< 关于 C# 中“接口”的一些小结 > 对于 C# 这样的不支持多重继承的语言,很好的体现的层次性,但是有些时候多重继承的确有一些用武之地. 比如,在 Stream 类 . 图形设备 ...
- netty中的Channel、ChannelPipeline
一.Channel与ChannelPipeline关系 每一个新创建的 Channel 都将会被分配一个新的 ChannelPipeline.这项关联是永久性 的:Channel 既不能附加另外一个 ...
- Java NIO中的Channel接口
1. Channel 通道,可以将指定文件的部分或全部直接映射成Buffer. 不能直接读写Channel中的数据,Channel只能与ByteBuffer交互. 读数据时,把Channel中的数据 ...
- C#中SqlDataAdapter的使用小结---转载
C#中SqlDataAdapter的使用小结 转载 叁木-Neil 最后发布于2018-06-07 21:29:39 阅读数 8275 收藏 展开 SqlDataAdapter对象 一.特点介绍1.表 ...
- 在.NET Core中使用Channel(一)
我最近一直在熟悉.net Core中引入的新Channel<T>类型.我想在它第一次发布的时候我了解过它,但是有关文章非常非常少,我不能理解它们与其他队列有什么不同. 在使用了一段时间后, ...
- netty系列之:netty中的Channel详解
目录 简介 Channel详解 异步IO和ChannelFuture Channel的层级结构 释放资源 事件处理 总结 简介 Channel是连接ByteBuf和Event的桥梁,netty中的Ch ...
随机推荐
- oracle中plsql练习-----在控制台输出1到100以内的素数。
一.思路:首先需要知道素数的概念即质数定义为在大于1的自然数中,除了1和它本身以外不再有其他因数. 中心思想是,外循环所有的自然数,内循环折半查询,增加代码的速度,注意:从1开始,需要大于1,但是pl ...
- C# lock 语法糖实现原理--《.NET Core 底层入门》之自旋锁,互斥锁,混合锁,读写锁
在多线程环境中,多个线程可能会同时访问同一个资源,为了避免访问发生冲突,可以根据访问的复杂程度采取不同的措施 原子操作适用于简单的单个操作,无锁算法适用于相对简单的一连串操作,而线程锁适用于复杂的一连 ...
- Git 的简单使用及ssh配置问题-赖大大
软件安装 第一步当然是安装啦. 官方网址:https://git-scm.com/ 具体操作 在你本地电脑的文件夹里右击鼠标,选Git base here 显然,你是在本地仓库的master分支上,通 ...
- mysql8 修改root密码
Navicat工具里选中mysql数据库 执行: ALTER user 'root'@'localhost' IDENTIFIED BY 'newpassward'; //newpassward 新密 ...
- uni-app 遮罩模板
1. common新建mask.vue文件. <template> <view> <view class="cpt-mask"> </vi ...
- ASP.NET Core去掉HTTPS配置和SSL证书
如果你的项目一不小心配置了https 右击项目=>属性=>调试=>启用SSL=>选择去掉 测试
- 【webpack 系列】基础篇
Webpack 基础篇 基本概念 Webpack 是一个现代 JavaScript 应用程序的静态模块打包器.当 webpack 处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每 ...
- leetcode 签到 面试题 17.16. 按摩师 动态规划
题目: 一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接.在每次预约服务之间要有休息时间,因此她不能接受相邻的预约.给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长 ...
- OpenCV-Python 改变颜色空间 | 十三
目标 在本教程中,你将学习如何将图像从一个色彩空间转换到另一个,像BGR↔灰色,BGR↔HSV等 除此之外,我们还将创建一个应用程序,以提取视频中的彩色对象 你将学习以下功能:cv.cvtColor, ...
- Selenium系列(十六) - Web UI 自动化基础实战(3)
如果你还想从头学起Selenium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1680176.html 其次,如果你不懂前端基础知识, ...