简析Go语言中同步
Go语言在提供CSP并发模型原语的同时,标准库的sync包也提供了针对传统基于共享内存并发模型的基本同步原语,包括互斥锁(sync.Mutex)、读写锁(sync.RWMutex)、条件变量(sync.Cond)等。
Go语言提倡“不要通过共享内存来通信,而应该通过通信来共享内存”,建议大家优先使用CSP并发模型进行并发程序设计。但是下面的一些场景中,依然需要sync包提供的低级同步原语:
一、需要高性能的临界区同步机制场景
channel属于高级同步原语,其实现是建构在低级同步原语之上。因此,channel自身的性能与低级同步原语相比要略微逊色。所以在需要高性能的临界区(critical section)同步机制的情况下,sync包提供的低级同步原语比较合适。
下面针对sync.Mutex和channel实现临界区同步机制做一下性能对比:
import (
"sync"
"testing"
)
var data struct {
i int
}
var mu sync.Mutex
var c = make(chan struct{}, 1)
func addSyncByMutex() {
mu.Lock()
data.i++
mu.Unlock()
}
func addSyncByChan() {
c <- struct{}{}
data.i++
<-c
}
func BenchmarkAddSyncByMutex(b *testing.B) {
for i := 0; i < b.N; i++ {
addSyncByMutex()
}
}
func BenchmarkAddSyncByChan(b *testing.B) {
for i := 0; i < b.N; i++ {
addSyncByChan()
}
}
> go test -bench . -benchmem go-sync-test.go
goos: windows
goarch: amd64
cpu: Intel(R) Xeon(R) CPU E5-2670 v2 @ 2.50GHz
BenchmarkAddSyncByMutex-20 53490476 23.26 ns/op 0 B/op 0 allocs/op
BenchmarkAddSyncByChan-20 18415152 61.56 ns/op 0 B/op 0 allocs/op
PASS
ok command-line-arguments 3.528s
从运行结果可以看出,sync.Mutex实现同步机制的性能要比channel实现同步机制的性能高出接近2倍。
二、不想转移结构体对象所有权,但又要保证结构体内部状态数据的同步访问的场景
基于channel的并发设计的一个特点是:在goroutine间通过channel转移数据对象的所有权,只有拥有数据对象所有权(从channel接收到该数据)的goroutine才可以对数据对象进行状态变更。如果程序中没有转移结构体对象所有权,但又要保证结构体内部状态数据能在多个goroutine之间同步访问,可以使用sync包提供的低级同步原语来实现,如sync.Mutex
// 用channel实现对stack数据的同步访问,转移结构体对象所有权
type stack struct {
data []interface{}
mu chan struct{}
}
func newStack() *stack {
return &stack{
mu: make(chan struct{}, 1),
}
}
func (s *stack) Push(v interface{}) {
s.mu <- struct{}{}
s.data = append(s.data, v)
<-s.mu
}
func (s *stack) Pop() interface{} {
s.mu <- struct{}{}
if len(s.data) > 0 {
length := len(s.data)
v := s.data[length-1:]
s.data = s.data[:length-1]
<-s.mu
return v
} else {
<-s.mu
return nil
}
}
// 用sync.Mutex实现对stack数据的同步访问,不转移结构体对象所有权
type stack struct {
data []interface{}
mu sync.Mutex
}
func (s *stack) Push(v interface{}) {
s.mu.Lock()
s.data = append(s.data, v)
s.mu.Unlock()
}
func (s *stack) Pop() interface{} {
s.mu.Lock()
defer s.mu.Unlock()
if len(s.data) > 0 {
length := len(s.data)
v := s.data[length-1:]
s.data = s.data[:length-1]
return v
} else {
return nil
}
}
Go语言基础及实战 文章被收录于专栏
Go语言学习笔记、语法知识、技术要点和个人理解及实战
查看30道真题和解析