聊聊Go的goroutine和Channel
- 前言
- 一、goroutine
- 定义
- 先看案例知道goroutine怎么用
- 是什么
- 二、channel
- 基础用法
- 将channel作为参数传递
- 创建多个channel
- 将channel作为返回值
- buffer channel
- channel关闭
相关文章推荐:《聊聊Go的并发编程 (二)》
前言
在之前学习go语言时,在看到groutine和channel时就直接跳过了。
当时根本没当回事,这么复杂看它干嘛!(当时的心态)
最近在看go的并发编程,发现全是用的这块内容,那么就只能硬着头皮来了,但是你会发现看着看着其实没那么难。
有时候不想看的东西可以先放着,等自己的注意力集中后在进行查看,你会得到意想不到的收获。
今天这篇文章是一个简单的讲解,咔咔也报了一个go的课程,在哪个课程里边看还能不能获取到更多的理解,随后在进行深度的补充。
- 给函数前加上go即可
- 不需要在定义是区分是否是异步函数
- 调度器在合适的点进行切换,这个点是有很多的,这里只是参考,不保证切换,不能保证在其它地方不会被切换。IO操作、channel、等待锁、函数调用、runtime.Gosched()等。。。
- 使用race来检测数据访问冲突
这个案例就是一个简单并发执行的代码,在go里边也就是一个关键字go即可。
从上图可以看到这行代码什么都没有输出,直接就退出了,那这到底是什么情况呢?
直接退出的原因,就是因为我们代码中的main和fmt打印是并发执行的,fmt还没来的急打印数据,外层的循环就已经循环结束了,然后就直接退出了。
在go语言中呢!假设一个main函数退出后,会直接杀死所有的goroutine,所以就造成的现象是,goroutine还没来的急打印数据就被退掉了。
那么你是不是会想,要怎么样才能看到打印的数据呢?其实也很简单,就是让main函数执行完成之后不要着急的退出,给一点等待的时间。看案例
在本案例中开的goroutine是10个,那么改为1000会怎么样呢?
结果显示还是正常显示,就类似与有1000个人在同时打印东西。
对操作系统熟悉的应该都知道,开10个线程没有问题,开100个线程也没什么大的问题,但是已经差不多了。
一般系统开几十个线程就可以了,那么如果要1000个人同时做一件事情就不能用线程来解决了,需要通过异步方式。
但是在go语言中呢!直接使用go关键字即可,就可以并发执行。
接下来就聊聊为什么go就可以同时1000进行打印。
协程你可以理解为轻量级的线程
,非抢占式多任务处理,由协程主动交出控制权
。
线程大家应该都知道是可以被操作系统在任何时候进行切换,所以说线程就是抢占式多任务处理,线程是没有控制权,哪怕是一个语句执行到一半都会被操作系统切掉,然后转到其它线程去操作。
那么反之对于协程来说,什么时候交出控制权,什么时候不交出控制权是由协程内部主动决定的,正是因为这种非抢占式,所以被称之为轻量级。
在第一节中了解到,在go中是可以开非常多的goroutine的,那么goroutine之间的双向通道就是channel
从上图案例中可以看到可以直接使用make函数来进行创建channel。
可以看到此时已经报错了,错误的意思就是在往channel发送1的时候会发生死锁。
在上文我们已经说了,channel是goroutine与goroutine之间的一个交互。
但是此时的案例中缺只有一个goroutine,所以还需要一个另一个goroutine来接收它。
现在你应该了解到如何开启一个goroutine了。
在上图中我们新开启了另一个goroutine,然后用了一个死循环来接受channel发送的值,并将其打印出来。
但是你会发现我们往channel中发送了俩个数据,此时的打印结果却只有一条数据。但总比我们刚开始的好多了,对吧!
可以理理代码的执行流程,先往channel发送了一个1,然后循环获取到第一个值并打印。
再往channel发送数据2,但还没来得及打印就直接退出了,这也就造成了只显示了数据1而没有显示数据2的现象。
那就是给函数channelDome加一个延迟退出的时间即可。
在上文中可以看到go后边跟的是一个闭包函数,在这个闭包中使用的c就是使用的外层的c。
那么将这个c使用参数传递可否呢?答案是肯定可以的。
通过上图可以看到不仅仅传递了channel还传递了id参数,同时还可以将代码直接优化为圈住的部分,也就是直接从channel取值。
从上图可以看到每个人都有自己的channel,然后进行分发,分发之后每个人都会收到自己的接收到的值并打印出来。
同样你可以看到我们在26行处还新加了一个for循环给channle里边发送数据。
从运行结果中你会发现打印的顺序是混乱的,例如receive i 和receve I这俩个值。
此时你会不会有疑问,我们在往channel中发送数据时是按照顺序发送的啊!那么接收时肯定也是按照顺序接收的。
既然非常确定发送数据是按照顺序的,那么问题就只能出现在Printf这里。
因为Printf是存在IO的,goroutine进行调度,那么此时的Printf是乱序的,但是都会将收到的值一一打印出来。
前几节的案例都是通过创建好的channle然后作为参数传递进去的。
那么本节将会把channel作为一个返回值给返回出去。
package mainimport (
"fmt"
"time")func createWorker(id int) chan int {
c := make(chan int)
go func() {
for {
fmt.Printf("Worker %d receive %c\n", id, <-c)
}
}()
return c}func channelDemo() {
var channels [10]chan int
for i := 0; i < 10; i++ {
channels[i] = createWorker(i)
}
for i := 0; i < 10; i++ {
channels[i] <- 'a' + i }
time.Sleep(time.Millisecond)}func main() {
channelDemo()}
从这里你可以看到我们将worker函数改为了createWorker函数,因为在这个函数里边就是直接创建channel。
接着通过一个协程将channel接收到的值进行打印。
通过运行结果可以得知我们的代码编写还是对的,但是此时返回的channel你可以非常直观的看到怎么用
但如果代码数量多的时候,你根本不清楚这个channel怎么用,所有这段代码还需要简单的修饰一下。
通过上述代码可以得知,是往channel中发送数据的,那么在createWorker
方法的返回的channel要标记一下
所以说现在的代码就变成这个样子,我们直接给createWorker方法的返回值channel标记好方向。作用是送数据的。
那么在打印的时候就是收据,这样看起来就非常直观了。
当修改完上面俩步之后你会发现createWorker
调用是报错了,Cannot use 'createWorker(i)' (type chan<- int) as type chan int
看到错误就应该知道是俩边类型不对等。
package mainimport (
"fmt"
"time")func createWorker(id int) chan<- int {
c := make(chan int)
go func() {
for {
fmt.Printf("Worker %d receive %c\n", id, <-c)
}
}()
return c}func channelDemo() {
var channels [10]chan<- int
for i := 0; i < 10; i++ {
channels[i] = createWorker(i)
}
for i := 0; i < 10; i++ {
channels[i] <- 'a' + i }
time.Sleep(time.Millisecond)}func main() {
channelDemo()}
学习了这么久了,那么咔咔问你一个问题,这段代码执行会发生什么?
没错,会发生报错,因为在文章开头咔咔就讲过了,给一个channel发送数据,就需要开启另一个协程来收数据。
虽然协程我们说是轻量级的,但是如果发送了数据之后,就需要切换协程来进行收数据就非常的耗费资源。
创建了可以有3个缓冲区的channel,然后往channel发送3个数据。
同时运行结果也可以得知没有在发生deadlock
。
给你一个问题,如果往缓冲区在发送一个数据4会发生什么呢?
聪明的你,肯定就想到结果了,没错,报了deadlock
接着我们就使用之前的worker,来接受channel的数据。
但是你会发现运行结果还是没有打印出送进去的1,2,3,4。
这个问题现在也已经说了好几次了,你可以试着问一下你自己,这种情况你应该怎么解决。
也就是加一个延迟时间即可,这里顺便给大家说明一下,之前的那个案例打印的是字母,所有格式化用的%c,现在打印的是数字,所以改为了%d,一点小小的改动。
这种方式建立channel对性能的提升是有一定的作用的。
到现在你有没有发现一个问题,那就是在发送channel时不知道什么时候发完了。
跟上节代码不一致的是,我们在结尾处添加了close,close需要注意的是在发送方进行关闭的。
你会看到运行结果并不如意,你会发现虽然收到了1,2,3,4。
但是下面还接收到了非常多的0,只是截图只截到了一条数据而已。
虽然发送方将channel给close掉了,但是接受放也就是worker还是会收到数据的,不是说channel给close后就收不到数据了。
但是当发送方将channle设置为close之后,收到的数据就都是0,也就是收到的是worker方法传递的c chan int这个参数0的值。
现在我们的channel是一个int类型,收到的是0。那么如果是一个string类型,收到的就是一个空字符串。
如果让你改这段程序你有没有思路呢?如果没有思路就跟这咔咔的节奏一起摇摆。
在函数worker中,使用俩个值来进行接收,n就是传递过来的channel c。ok就是判断这个值是否存在。
坚持学习、坚持写作、坚持分享是咔咔从业以来一直所秉持的信念。希望在偌大互联网中咔咔的文章能带给你一丝丝帮助。我是咔咔,下期见。
以上就是聊聊Go的并发编程 (一)的详细内容,更多请关注亿码酷站其它相关文章!
聊聊Go的并发编程 (一)
—–文章转载自PHP中文网如有侵权请联系ymkuzhan@126.com删除
转载请注明来源:聊聊Go的并发编程 (一)
本文永久链接地址:https://www.ymkuzhan.com/31867.html 下载声明:
本站资源如无特殊说明默认解压密码为www.ymkuzhan.com建议使用WinRAR解压;本站资源来源于用户分享、互换、购买以及网络收集等渠道,本站不提供任何技术服务及有偿服务,资源仅提供给大家学习研究请勿作它用。赞助本站仅为维持服务器日常运行并非购买程序及源码费用因此不提供任何技术支持,如果你喜欢该程序,请购买正版!版权声明:
下载本站资源学习研究的默认同意本站【版权声明】若本站提供的资源侵犯到你的权益,请提交版权证明文件至邮箱ymkuzhan#126.com(将#替换为@)站长将会在三个工作日内为您删除。免责声明:
您好,本站所有资源(包括但不限于:源码、素材、工具、字体、图像、模板等)均为用户分享、互换、购买以及网络收集而来,并未取得原始权利人授权,因此禁止一切商用行为,仅可用于个人研究学习使用。请务必于下载后24小时内彻底删除,一切因下载人使用所引起的法律相关责任,包括但不限于:侵权,索赔,法律责任,刑事责任等相关责任,全部由下载人/使用人,全部承担。以上说明,一经发布视为您已全部阅读,理解、同意以上内容,如对以上内容持有异议,请勿下载,谢谢配合!支持正版,人人有责,如不慎对您的合法权益构成侵犯,请联系我们对相应内容进行删除,谢谢!