Go语言并发
1.并发与并行的区别
并发:多线程程序在一个核的CPU上运行,主要通过时间片的切换实现。
并行:多线程程序在多个核的CPU上运行,
广义上可以认为并行是实现并发的一种手段。
1.1Goroutine
协程:用户态,轻量级线程,栈==KB==级别,创建和调度由go语言本身完成
线程:内核态,线程跑多个协程,栈**==MB==**级别
1.2 CSP(Communicating Sequential Processes)
Go语言提倡通过通信来共享内存,而不是通过共享内存实现通信.
此处go语言的通信就是通过channel实现的
1.3Channel
声明:make(chan 元素类型,[缓冲区大小])
根据缓冲区的有无,可将channel分为
- 无缓冲channel:make(chan int),也被成为同步通道
- 有缓冲channel:make(chan int , 2)
channel主要应用场景:
channel是 goroutine 与 goroutine 之间通信的重要桥梁,借助 channel,我们能很轻易的写出一个多协程通信程序。今天,我们就来看看这个 channel 的常用的应用场景。
- 数据交流:当作并发的buffer或者queue,解决生产者-消费者问题。多个goroutine可以并发当作生产者(Producer)和消费者(Consumer)。
- 数据传递:一个goroutine将数据交给另一个goroutine,相当于把数据的拥有权(引用)托付出去。
- 信号通知:一个goroutine可以将信号(closing、closed、data ready等)传递给另一个或者另一组goroutine 。
- 任务编排:可以让一组goroutine按照一定的顺序并发或者串行的执行,这就是编排的功能。
- 锁:利用Channel也可以实现互斥锁的机制。
- 控制并发数:
- 定时任务:
1.4 并发安全 Lock
Lock锁机制,对共享资源进行操作前,先对资源加锁,然后进行操作,操作完成后释放锁,依次类推,在每次需要获取该共享资源时,都需要进行锁操作。
在go语言中,锁操作时要通过sync.Mutex实现的,
简单示例:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var lock sync.Mutex
x := 0
go func(x *int) {
for i := 0; i < 10; i++ {
lock.Lock()
*x += 1
lock.Unlock()
}
}(&x)
x += 100
time.Sleep(time.Second)
fmt.Println(x)
}
优点:能够保证并发安全,
缺点:加锁和释放锁的过程比较耗费系统资源,同时如果操作不当,容易引发死锁
1.5 WaitGroup
WaitGroup是Golang应用开发过程中经常使用的并发控制技术。
WaitGroup,可理解为Wait-Goroutine-Group,即等待一组goroutine结束。比如某个goroutine需要等待其他几个goroutine全部完成,那么使用WaitGroup可以轻松实现。
下面程序展示了一个goroutine等待另外两个goroutine结束的例子:
package main
import (
"fmt"
"time"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(2) //设置计数器,数值即为goroutine的个数
go func() {
//Do some work
time.Sleep(1*time.Second)
fmt.Println("Goroutine 1 finished!")
wg.Done() //goroutine执行结束后将计数器减1
}()
go func() {
//Do some work
time.Sleep(2*time.Second)
fmt.Println("Goroutine 2 finished!")
wg.Done() //goroutine执行结束后将计数器减1
}()
wg.Wait() //主goroutine阻塞等待计数器变为0
fmt.Printf("All Goroutine finished!")
}
简单的说,上面程序中wg内部维护了一个计数器:
- 启动goroutine前将计数器通过Add(2)将计数器==设置为待启动的goroutine个数。==
- 启动goroutine后,使用Wait()方法阻塞自己,等待计数器变为0。
- 每个goroutine执行结束通过Done()方法将计数器减1。
- 计数器变为0后,阻塞的goroutine被唤醒。
其实WaitGroup也可以实现一组goroutine等待另一组goroutine,这有点像玩杂技,很容易出错,如果不了解其实现原理更是如此。实际上,WaitGroup的实现源码非常简单。
总结
go语言通过goroutine实现并发,它是协程,一种轻量化的线程,是用户态,产生和调度由go语言本省处理。go语言实现并发访问控制的三种方式:channel,sync Mutex,WaitGroup