大家一起学Golang——并发编程、goroutine、channel
Goroutine
并发channel
并发的同步模型
并发编程
在早期的时候,CPU都是单核顺序执行机器指令,随着计算机处理器的发展,由单核上升到多核,编程语言也朝着并行化方向发展,Go语言这是在这种情况下诞生的原生支持并发的编程语言。
并行编程的模型有多线程、消息传递等,Go是基于CSP模型的并发编程,通过go关键字启动Goroutine实现并发。
CSP通信顺序进程:是在不同Gotoutine之间传递消息,不是对数据进行加锁来实现同步访问。
并发(Concurrency):逻辑上具有同时处理多个任务的能力,可理解一个CPU同时处理多个任务。
并行(Parallelism):物理上在同一时刻执行多个并发任务,可以理解多个cpu同时执行任务。
并发Goroutine
Goroutine可以看作是轻量级的线程,与系统级线程有些不同。系统级线程有自己固定大小(2M)的栈,用来保存函数参数和局部变量。对于需求小栈的线程是一种浪费,需要大栈空间的可能会面临栈溢出问题。Gotoutine默认栈空间2K大小,可以动态调整栈大小(可达1G),go轻松启动成千上万个Goroutine,并发编程变得更容易了。
Go语言运行时会把Goroutine调度到逻辑处理器上运行,逻辑处理器绑定到操作系统的线程上,当Goroutine准执行时,会放到逻辑处理器的执行队列中,等待执行。
一般来说对于较少的计算资源,并发要比并行效果要好,若要Goroutine并行,可用多个逻辑处理器,均匀分布处理Goroutine。(就是上图多个这种调度)
go调用Goroutine
go func(){
fmt.Println("Goroutine")
}
channel通道
Go是基于CSP模型并发的编程语言,不要通过共享内存来通信,要通过通信来共享内存。channel是通信的管道,是Goroutine之间进行同步的方法。
channel具有方向、数据类型和Buffer大小。
var int chan = make(chan int)
//单向chan定义方式 函数传递的安全考虑
//接收
ch:=make(chan<- int)
//发送
ch:=make(<-chan int)
//双向通道,设置channel大小
ch:=make(chan int, 10)
- 有缓存和无缓冲的区别
无缓冲要求发送和接收 必须同时准备好,才能完成发送和接收属于同步操作,否则发生阻塞等待。
可以接收多个值,不要求同时接发,属于异步操作。
ch := make(chan int )
go func() {
value := <-ch
fmt.Println(value)
}()
ch <- 110
//通道不使用时可以close进行关闭
close(ch)
- channel小结:
- 向已经close的channel写数据,重复关闭channel或关闭为nil的channel会引发panic
- channel为nil,收发会阻塞
- 同时监听多个channel或避免阻塞在channel上,可以配合select使用,同时多个channel满足条件的情况下,select可以随机选择
- 一般原则,写入方负责关闭channel,读取方负责检测channel是否关闭。
并发同步模型
- Mutex互斥锁
互斥锁在代码上创建一个临界区,保证同一时间只有一个goroutine可以执行临界区的代码。
var m sync.Mutex
m.Lock()
defer m.Unlock() //推荐defer解锁 - RWMutex读写锁
比Mutex多区分读锁和写锁,多个读可以并行,读写不能并⾏,适用于读多写少的场景。 - WaitGroup组等待
适合于一对多的 goroutine 的协同等待
三个方法:Add Done Wait
当计数大于0,Wait()会一直阻塞等待,直到Done执行将计数为0。
var m sync.Mutex
v := 0
wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
m.Lock()
defer m.Unlock()
defer wg.Done()
v++
}()
}
wg.Wait()
fmt.Println(v)