Go语言基础之并发!
Go语言基础之并发!
月伴飞鱼Go语言中的并发通过
goroutine
实现。
goroutine
类似于线程,属于用户态线程
- 可以根据需要创建成千上万个
goroutine
并发工作。
goroutine
是由Go语言的运行时(runtime)调度完成,而线程是由操作系统调度完成。Go语言还提供
channel
在多个goroutine
间进行通信。
Goroutine
Go程序会将
goroutine
中的任务合理地分配给每个CPU。当需要让某个任务并发执行的时候
- 只需要把这个任务包装成一个函数,开启一个
goroutine
去执行这个函数就行。一个
goroutine
必定对应一个函数,可以创建多个goroutine
去执行相同的函数。
启动单个goroutine
使用
goroutine
只需要在调用函数的时候在前面加上go
关键字
- 就可以为一个函数创建一个
goroutine
。
1 | func hello(){ |
在程序启动时,Go程序就会为
main()
函数创建一个默认的goroutine
。当
main()
函数返回的时候该goroutine
就结束了
- 所有在
main()
函数中启动的goroutine
会一同结束。
出让资源
通过
runtime.Gosched()
出让资源,让其他goroutine
优先执行。
1 | func main() { |
自杀
通过
runtime.Goexit()
实现自杀,自杀前会执行提前定义的defer语句
- 同时调用它的
goroutine
也会跟着自杀。
1 | func test(){ |
主
goroutine
结束后,会带走未结束的子goroutine
。同时如果主
goroutine
暴毙,会令所有的子goroutine
失去牵制,等所有的子goroutine
都结束后程序会崩溃:
fatal error: no goroutines (main called runtime.Goexit) - deadlock!
。
启动多个Goroutine
使用
sync.WaitGroup
来实现goroutine
的同步。
1 | var wg sync.WaitGroup |
Channel
channe1
可以让一个goroutine
发送特定值到另一个goroutine
的通信机制。Go语言中的通道(
channel
)是一种特殊类型,通道像一个传送带或者队列
- 总是遵循先进先出的规则,保证数据的收发顺序
每一个通道都是一个具体类型的导管,也即是声明
channel
时候需要为其制定元素类型。
channel类型
channel
是一种类型,一种应用类型,声明通道类型的格式如下:
1 | var 变量 chan 元素类型 |
创建channel
通道是引用类型,通道类型的控制是
nil
1 | var ch chan int |
声明的通道需要使用
make
函数初始化后才能使用创建
channel
的格式如下:
1 | make(chan 元素类型,[缓冲大小]) |
channel操作
通道有读、写和关闭三种操作。
读和写都是用
<-
符号。
1 | // 初始化一个channel |
channel类型
channel
分不带缓冲区的
channel和带缓冲区的
channel。
无缓冲区
无缓冲
channel
从无缓冲的channel
中读取消息会阻塞
- 直到有
goroutine
向该channel
中发送消息。同理,向无缓冲区的
channel
中发送消息也会阻塞
- 直到有
goroutine
从channel
中读取消息。使用无缓冲通道进行通道将导致发送和接收的
goroutine
同步化
- 因此无缓冲通道也被称为
同步通道
。
1 | func recv(c chan int){ |
有缓存的通道
有缓存的
channel
的声明方式为指定make
函数的第二个参数
- 该参数为
channel
缓存的容量。有缓存的
channel
类似于一个阻塞队列(采用环形数组实现)。当缓存未满时,向
channel
中发送消息不会阻塞
- 当缓存满时,发送操作将会阻塞,直到有其他
goroutine
从中读取消息。相应的,当
channel
中消息不为空是,读取消息不会出现阻塞
- 当
channel
为空时,读取操作会发生阻塞,直到有goroutine
向channel
中写入消息。可以通过使用内置的
len()
函数获取通道内元素的数量
- 使用
cap()
函数获取通道的容量。
1 | func main() { |
互斥锁
有时候在Go代码中可能存在多个
goroutine
同时操作一个资源(临界区)
- 这种情况会发生
竟态问题
(数据竟态)。互斥锁是一种常用的控制共享资源访问的方法
- 它能够保证同时只有一个
goroutine
可以访问共享资源。Go语言中使用
sync
包的Mutex
类型来实现互斥锁。
1 | func main() { |
通过信号量控制并发数
控制并发数属于常用的调度,规定并发的任务都必须现在某个监视管道中进行注册
- 而这个监视管道的缓存能力是固定的
- 比如说5,那么注册在该管道中的并发能力也是5。
1 | var sema chan int |