简析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语言学习笔记、语法知识、技术要点和个人理解及实战