Go sync 并发包Demo详解

go的sync 主要是为了帮助我们处理go 并发提供的工具包,里面包含了丰富的处理方法,今天我们就一起撸一遍常用工具的使用。

Go sync 并发包Demo详解

sync.Mutex

这个是最常用的锁,这个是互斥锁

mutex := &sync.Mutex{}

mutex.Lock()
// 临界区代码
mutex.Unlock()

这里有个细节,上面使用mutex 的指针形式,这个非常重要,如果在函数调用的时候,这个锁也需要通过指针传递。因为Go 的赋值是值传递,经常会有初学者将mutex 通过值传递的方式方式传入到函数里面,导致函数内外使用了两个锁。

sync.RWMutex

相比上面的Mutex 同时只能有一个协程获取锁,RWMutex 可以允许多个协程获取读锁,从而在读多写少的场景中,相比 Mutex 性能更优。

mutex := &sync.RWMutex{}

mutex.Lock()
// Update shared variable
mutex.Unlock()

mutex.RLock()
// Read shared variable
mutex.RUnlock()

当然读写之间或者多个写之间也是互斥的。

sync.WaitGroup

WaitGroup相比手动一个一个创建goroutine,并且等待每个gorotine执行完成,WaitGroup提供了一个协程工具包,能够快速地启动多个协程。

wg := &sync.WaitGroup{}

for i := 0; i < 8; i++ {
  wg.Add(1)
  go func() {
    defer  wg.Done()
    // Do something
  }()
}

wg.Wait()

Add 是计数器,表示启动协程的个数,我们也可以直接在for 循环之外直接Add(8) 效果也是一样的。每个协程执行完成后,执行Done 表示计数器减一。Wait 则是等待计数器归零,也就是等待之前Add 的计数全部Done了。这里的Wait 会阻塞,直到所有的任务都完成。

sync.Map

老版本的go 并不支持并发map,在面对map并发的时候,都是借助上面介绍的RWMutex 封装一个并发map,从而避免map访问的时候并发冲突。在引入sync map 就可以更加简单的方式操作map了


m := &sync.Map{}

// 插入元素
m.Store(1, "one")
// 获取元素
value, contains := m.Load(1)
if contains {
  fmt.Printf("%s
", value.(string))
}

// 删除元素
m.Delete(1)

//遍历元素
m.Range(func(key, value interface{}) bool {
  fmt.Printf("%d: %s
", key.(int), value.(string))
  return true
})

并发map 使用的最大缺点个人感觉使用了interface的方式获取元素,每次get 到vaule 后还需要通过类型判断,转成对应的类型,操作起来不是很方便。

sync.Pool

为了降低GC 的开销,我们可以建一个对象池,譬如我们在接收数据包的时候,我们可以建立一个数组池子,用完再放回池子。比如下面的代码

func writeFile(pool *sync.Pool, filename string) error {
	// 获取一个buf 
	buf := pool.Get().(*bytes.Buffer)
	// 使用完将 buf 还给pool
	defer pool.Put(buf)

	// 清空buf
	buf.Reset()

	buf.WriteString("foo")

	return ioutil.WriteFile(filename, buf.Bytes(), 0644)
}

这个相当于程序内部自己维护了一个内存小池子,避免每次向操作系统申请内存。除此之外处理数据库连接的时候也经常使用 pool。

pool := &sync.Pool{
  New: func() interface{} {
    return NewConnection()
  },
}

connection := pool.Get().(*Connection)

避免了每次都通过new 创建connection。当有连接释放了,这个对象内存就归还pool。

sync.Once

once 也是经常使用的函数,也经常在面试题中出现:Go里面怎么保证某段代码只执行一次。

once := &sync.Once{}
for i := 0; i < 4; i++ {
	i := i
	go func() {
		once.Do(func() {
			fmt.Printf("first %d
", i)
		})
	}()
}

上面的代码虽然for 循环了 4次但只会执行一次。

sync.Cond

条件变量cond 使用的频率就没有那么高了。它的主要目的是一对多,当满足某个条件后,多个协程都可以响应。条件变量依赖锁,下面是创建一个条件变量。

cond := sync.NewCond(&sync.RWMutex{})

我们可以模拟一个场景,满足条件的情况下打印,然后启动多个协程去打印


func printFirstElement(s []int, cond *sync.Cond) {
	cond.L.Lock()
	cond.Wait() //等待条件变量
	fmt.Printf("%d
", s[0])
	cond.L.Unlock()
}

s := make([]int, 1)
for i := 0; i < runtime.NumCPU(); i++ {
	go printFirstElement(s, cond)
}

此时,即便获取到了锁,也不能打印,会hang 在wait 的地方。

有两种方式能让wait 继续执行,一种方式是通过 Signal唤醒一个协程

cond.L.Lock()
s[0] = i
cond.Signal()
cond.L.Unlock()

另一种方式是通过Broadcast 唤醒所有的协程。

cond.L.Lock()
s[0] = i
cond.Broadcast()
cond.L.Unlock()

其实高级和初级开发的区别就在于能够针对各种场景使用不同的工具方法,熟练掌握这些工具的使用,经常能够实现锦上添花的效果。

展开阅读全文

页面更新:2024-04-13

标签:个协   池子   工具包   指针   变量   计数器   详解   函数   场景   元素   对象   内存   条件   效果   代码   方式   科技

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top