Go语言中通过Groutine 启动一个Go协程,不同协程之间是并发执行的,就像C++/Java中线程之间线程安全是一个常见的问题。
如下Go 语言代码:
func TestConcurrent(t *testing.T) {
var counter int = 0
for i := 0;i < 5000; i ++{
go func() { // 启动groutine 进行变量的自增
counter ++
}()
}
time.Sleep(time.Second * 1)
t.Logf("Counter = %d", counter)
}
最后的结果输出如下:
=== RUN TestConcurrent
concurrent_test.go:18: Counter = 4663
--- PASS: TestConcurrent (1.00s)
希望变量
Counter
的结果是5000,最终因为协程之间并发访问共享内存的原因导致部分协程执行的结果没有得到变更,从而数据不符合预期。
Go中提供了
Mutex
和
RWMutex
,来实现锁功能。前者就是正常的Lock, Unlock;后者提供了读写锁,在协程读写分离中提供了读读互不影响,读写互斥。
如下测试代码:
func TestMutex(t *testing.T) {
var counter int = 0
// var mu sync.Mutex // mutex lock
var mu sync.RWMutex // rwmutex lock
for i := 0;i < 5000; i ++{
go func() {
// defer mutex unlock
// 如果协程异常退出,能够释放锁
defer func() {
mu.Unlock()
}()
mu.Lock()
counter ++
}()
}
time.Sleep(time.Second * 1)
t.Logf("Counter = %d", counter)
}
最终输出如下,通过mutex 实现了变量访问的安全:
=== RUN TestMutex
concurrent_test.go:36: Counter = 5000
--- PASS: TestMutex (1.01s)
可以看到如上代码需要通过Sleep来让所有的协程都执行完,显然不合理。、
func TestWaitGroup(t *testing.T) {
var counter int = 0
var mu sync.RWMutex // rw lock, 读写互斥
var wg sync.WaitGroup
for i := 0;i < 5000; i ++{
wg.Add(1)
go func() {
defer func() {
mu.Unlock()
}()
mu.Lock()
counter ++
wg.Done() // wg.Add(-1)
}()
}
wg.Wait()
t.Logf("Counter = %d", counter)
}
=== RUN TestWaitGroup
concurrent_test.go:57: Counter = 5000
--- PASS: TestWaitGroup (0.00s)