goroutine的传统同步机制
goroutine是go的轻量级“线程",也叫协程。它有以下几个特点:
- 轻量级“线程”
- 非抢占式多任务处理,由协程主动交出控制权
- 编译器、解释器、虚拟机层面的多任务
- 多个携程可能在一个或多个线程上运行
go推荐的goroutine之间的同步机制是channel,基于CSP(Communication Sequential Process)模型,经典名:不要使用共享内存来通信,要使用通信来共享内存。
但是本篇文章并不将CSP模型,而是回到传统的同步机制:
WaitGroup
Mutex
Cond
针对其中的锁同步机制,goroutine如何实现:
package main
import (
"fmt"
"sync"
"time"
)
type atomicInt struct{
value int
lock sync.Mutex
}
func (atomic *atomicInt) increment() {
fmt.Println("对于AtomicInt进行+1操作!")
func() {
atomic.lock.Lock()
defer atomic.lock.Unlock()
atomic.value++
}()
}
func (atomic *atomicInt) get() int{
atomic.lock.Lock()
defer atomic.lock.Unlock()
return atomic.value
}
func main(){
var a atomicInt
a.increment()
go func() {
a.increment()
}()
time.Sleep(time.Millisecond)
fmt.Println(a.get())
}
9 ~ 12行定义了一个atomicInt结构体,value是值,lock是这个共享变量的锁。
14 ~ 23行定义了这个结构体的increment递增方法。18 ~ 22行是对这段代码加了锁,并且用了defer来最后执行释放锁操作,这里的lock操作仅仅只针对这段代码。
25 ~ 29行定义了结构体的get方法,同理也是加锁了。
31 ~ 39行的main函数代表了主协程,go func()代表子协程,他们共享了atomicInt变量,并且都执行了+1操作,最后主协程输出最新值。
结果:
但是如果我们去掉所有的加锁操作,代码如下:
执行go run -race automic.go跟踪如下:
警告主协程在22行有读操作,子协程在17行有写操作,二者是冲突的。
所以这种情况要加锁。