并发操作详解:Goroutines 和 Channels 的声明与使用
什么是进程、线程
进程就是一个应用程序的工作空间,比如你打开的 QQ,微信,工作空间包含了该程序运行所需的所有资源。而线程是进程中的执行单位,一个进程最少有一个线程。
进程与线程对比
进程是系统资源分配和调度的最小单位
线程是程序执行的最小单位
一个进程由一个或多个线程组成,线程是进程中代码的不同执行路线
进程之间相互独立,进程中的线程共享程序的内存空间及资源
线程在时间效率和空间效率都比进程要高
协程
协程是一种用户态的轻量级线程,线程是 CPU 来调度,而协程的调度完全是由用户来控制的。
协程与线程对比
一个线程可以有多个协程
线程、进程都是同步机制,而协程是异步
协程可以保留上一次调用时的状态,当过程重入时,相当于进入了上一次的调用状态
协程是需要线程来承载运行的,所以协程并不能取代线程,线程是被分割的 CPU 资源,协程是组织好的代码流程
并发、并行
并发和并行是相对于进程或者线程来说的。
并发是一个或多个 CPU 对多个进程/线程之间的多路复用,通俗讲就是 CPU 轮流执行多个任务,而每个任务都执行一小段,从宏观来看就像在同时执行。
并行必须有多个 CPU 来提供支持,真正意义上的在同一时刻执行多个进程或线程。
Go 语言协程
Go 中没有线程的概念,只有协程(goroutine),协程相比线程更加轻量,上下文切换更快。Goroutine 由 Go 自己来调度,我们只管启用。
goroutine 通过 go 关键字来启动,非常简单,go 关键字后面加一个方法或函数 go function()
运行结果:
Channel
channel(通道) 是用来解决多个 goroutine 之间通信问题的。
在 Go 语言中,提倡通过通信来共享内存,而不是通过共享内存来通信,其实就是提倡通过 channel 发送接收消息的方式进行数据传递,而不是通过修改同一个变量。所以在数据流动、传递的场景中要优先使用 channel,它是并发安全的,性能也不错。
channel 声明
ch := make(chan string)
使用 make 函数
chan 是关键字,表示 channel 类型,chan 是一个集合类型
string 表示 channel 里存放数据的类型
chan 使用
chan 只有发送和接收两种操作:
发送: <-chan //向 chan 内发送数据
接收: chan-> //从 chan 中获取数据
示例:
运行结果
有了 chan 后,我们可以不使用 Sleep 来等 goroutine 执行完了,因为接收操作(
value := <-ch
)会阻塞等待,直到它获取到值为止。
无缓冲 channel
上面的操作就是一个无缓冲 channel,通道的容量是 0,它不能存储数据,只是起到了传输的作用,所以无缓冲 channel 的发送和接收操作是同时进行的
有缓冲 channel
在声明的时候,我们可以传入第二个参数,即 channel 容量大小,这样就是创建了一个有缓冲 channel。
有缓冲 channel 内部有一个队列
发送操作是向队列尾部追加元素,如果队列满了,则阻塞等待,直到接收操作从队列中取走元素。
接收操作是从队列头部取走元素,如果队列为空,则阻塞等待,直到发送操作向队列追加了元素。
可以通过内置函数
cap
来获取 channel 的容量,通过内置函数 len 获取 channel 中元素个数。
关闭 channel
使用内置函数 close :close(ch)
channel 关闭了就不能再向其发送数据了,否则会引起 panic 异常。
可以从关闭了的 channel 中接收数据,如果没数据,则接收到的是元素类型的零值。
单向 channel
只能发送或者只能接收的 channel 为单向 channel。
单向 channel 声明
只需要在基础声明中增加操作符即可:
示例:
select+channel
select 可以实现多路复用,即同时监听多个 channel。
发现哪个 channel 有数据产生,就执行相应的 case 分支
如果同时有多个 case 分支可以执行,则会随机选择一个
如果一个 case 分支都不可执行,则 select 会一直等待
示例:
运行结果:
此处留作思考题,为何会这样输出呢?
版权声明: 本文为 InfoQ 作者【微客鸟窝】的原创文章。
原文链接:【http://xie.infoq.cn/article/f6d272f99d438585ce1ed0e74】。文章转载请联系作者。
评论