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

全部评论

相关推荐

伟大的烤冷面被普调:暨大✌🏻就是强
点赞 评论 收藏
分享
11-09 11:01
济南大学 Java
Java抽象带篮子:外卖项目真得美化一下,可以看看我的详细的外卖话术帖子
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务