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 ...
随机推荐
- MyBatis框架——缓存机制
使⽤缓存机制的作⽤也是减少 Java 应⽤程序与数据库的交互次数,从⽽提升程序的运⾏效率. ⽐如第 ⼀次查询出某个对象之后,MyBatis 会⾃动将其存⼊缓存,当下⼀次查询同⼀个对象时,就可以直接从 ...
- Python Django撸个WebSSH操作Kubernetes Pod
优秀的系统都是根据反馈逐渐完善出来的 上篇文章介绍了我们为了应对安全和多分支频繁测试的问题而开发了一套Alodi系统,Alodi可以通过一个按钮快速构建一套测试环境,生成一个临时访问地址,详细信息可以 ...
- 【转载】因为我们是OIer
我们是OIer, 所以我们 不用在跑道上挥汗如雨: 不用在球场上健步如飞: 更不用在没事的时候, 经受非人的体能训练-- 但是, 我们却要把头脑 高速运转, 还要接受一大堆 大学生也只是 " ...
- 【分布式锁】06-Zookeeper实现分布式锁:可重入锁源码分析
前言 前面已经讲解了Redis的客户端Redission是怎么实现分布式锁的,大多都深入到源码级别. 在分布式系统中,常见的分布式锁实现方案还有Zookeeper,接下来会深入研究Zookeeper是 ...
- C#中的9个“黑魔法”与“骚操作”
C#中的9个"黑魔法"与"骚操作" 我们知道C#是非常先进的语言,因为是它很有远见的"语法糖".这些"语法糖"有时过于好 ...
- 倒影box-reflect(可图片可文字)
需要写兼容写法: -webkit-box-reflect:below 3px -webkit-(repeating)linear/redial-gradient(...): 1.先写direction ...
- GANs和低效映射
生成对抗网络(GANs)被誉为生成艺术领域的下一纪元,这是有充分理由的.新技术一直是艺术的驱动因素,从颜料的发明到照相机再到Photoshop-GAN是自然而然的.例如,考虑下面的图片,由埃尔加马勒发 ...
- spring-cloud-gateway静态路由
为什么引入 API 网关 使用 API 网关后的优点如下: 易于监控.可以在网关收集监控数据并将其推送到外部系统进行分析. 易于认证.可以在网关上进行认证,然后再将请求转发到后端的微服务,而无须在每个 ...
- Prism 源码解读7-导航
介绍 Prism提供了一个非常强大的功能导航,导航的意思就是指定对应的View显示.这个导航的强大之处有: 可以设置导航前后的动作 可以指定View实例的生命周期,可以是否导航到新的View实例 提供 ...
- Html,Css遇到的bug2020031601
td,里面jquery设置height,设置成功也没有效果. div可以成功设置,并产生效果. 可能是td,div有一些隐藏的特性不同导致的.