当前位置:首页 > 科技  > 软件

Go 语言中 sync 包的近距离观察

来源: 责编: 时间:2023-11-28 09:34:53 137观看
导读让我们来看看负责提供同步原语的 Go 包:sync。sync.Mutexsync.Mutex 可能是 sync 包中被广泛使用的原语。它允许对共享资源进行互斥操作(即不允许同时访问):mutex := &sync.Mutex{}mutex.Lock()// Update shared variable

4SG28资讯网——每日最新资讯28at.com

让我们来看看负责提供同步原语的 Go 包:sync。4SG28资讯网——每日最新资讯28at.com

sync.Mutex

sync.Mutex 可能是 sync 包中被广泛使用的原语。它允许对共享资源进行互斥操作(即不允许同时访问):4SG28资讯网——每日最新资讯28at.com

mutex := &sync.Mutex{}mutex.Lock()// Update shared variable (e.g. slice, pointer on a structure, etc.)mutex.Unlock()

必须指出的是 sync.Mutex 无法被复制(就像 sync 包中的所有其他原语一样)。如果一个结构体有一个 sync 字段,必须通过指针进行传递。4SG28资讯网——每日最新资讯28at.com

sync.RWMutex

sync.RWMutex 是一个读写锁。它提供了与我们刚刚看到的 Lock() 和 Unlock() 相同的方法(因为这两个结构都实现了 sync.Locker 接口)。然而,它还允许使用 RLock() 和 RUnlock() 方法进行并发读取:4SG28资讯网——每日最新资讯28at.com

mutex := &sync.RWMutex{}mutex.Lock()// Update shared variablemutex.Unlock()mutex.RLock()// Read shared variablemutex.RUnlock()

一个 sync.RWMutex 允许至少一个读取者或正好一个写入者,而一个 sync.Mutex 则允许正好一个读取者或写入者。4SG28资讯网——每日最新资讯28at.com

让我们运行一个快速的基准测试来比较这些方法:4SG28资讯网——每日最新资讯28at.com

func BenchmarkMutexLock(b *testing.B) {    m := sync.Mutex{}    for i := 0; i < b.N; i++ {        m.Lock()        m.Unlock()    }}func BenchmarkRWMutexLock(b *testing.B) {    m := sync.RWMutex{}    for i := 0; i < b.N; i++ {        m.Lock()        m.Unlock()    }}func BenchmarkRWMutexRLock(b *testing.B) {    m := sync.RWMutex{}    for i := 0; i < b.N; i++ {        m.RLock()        m.RUnlock()    }}
BenchmarkMutexLock-4       83497579         17.7 ns/opBenchmarkRWMutexLock-4     35286374         44.3 ns/opBenchmarkRWMutexRLock-4    89403342         15.3 ns/op

正如我们注意到的那样,读取锁定/解锁 sync.RWMutex 比锁定/解锁 sync.Mutex 更快。另一方面,调用 Lock()/Unlock() 在 sync.RWMutex 上是最慢的操作。4SG28资讯网——每日最新资讯28at.com

总的来说,当我们有频繁的读取和不经常的写入时,应该使用 sync.RWMutex。4SG28资讯网——每日最新资讯28at.com

sync.WaitGroup

sync.WaitGroup 也经常被使用。它是一个 goroutine 等待一组 goroutine 完成的惯用方式。4SG28资讯网——每日最新资讯28at.com

sync.WaitGroup 拥有一个内部计数器。如果这个计数器等于 0,Wait() 方法会立即返回。否则,它会被阻塞,直到计数器变为 0。4SG28资讯网——每日最新资讯28at.com

要增加计数器,我们可以使用 Add(int) 方法。要减少计数器,可以使用 Done()(将计数器减 1)或者使用带有负值的相同的 Add(int) 方法。4SG28资讯网——每日最新资讯28at.com

在以下示例中,我们将启动八个 goroutine 并等待它们完成:4SG28资讯网——每日最新资讯28at.com

wg := &sync.WaitGroup{}for i := 0; i < 8; i++ {  wg.Add(1)  go func() {    // Do something    wg.Done()  }()}wg.Wait()// Continue execution

每次我们创建一个 goroutine 时,都会使用 wg.Add(1) 来增加 wg 的内部计数器。我们也可以在 for 循环外部调用 wg.Add(8)。4SG28资讯网——每日最新资讯28at.com

与此同时,每当一个 goroutine 完成时,它会使用 wg.Done() 来减少 wg 的内部计数器。4SG28资讯网——每日最新资讯28at.com

一旦执行了八个 wg.Done() 语句,主 goroutine 就会继续执行。4SG28资讯网——每日最新资讯28at.com

sync.Map

sync.Map 是 Go 中的一个并发版本的 map,我们可以:4SG28资讯网——每日最新资讯28at.com

  • 使用 Store(interface{}, interface{}) 添加元素
  • 使用 Load(interface) interface{} 检索元素
  • 使用 Delete(interface{}) 删除元素
  • 使用 LoadOrStore(interface{}, interface{}) (interface, bool) 检索或添加元素(如果之前不存在)。返回的 bool 值为 true 表示在操作前键存在于 map 中。
  • 使用 Range 在元素上进行迭代
m := &sync.Map{}// Put elementsm.Store(1, "one")m.Store(2, "two")// Get element 1value, contains := m.Load(1)if contains {  fmt.Printf("%s/n", value.(string))}// Returns the existing value if present, otherwise stores itvalue, loaded := m.LoadOrStore(3, "three")if !loaded {  fmt.Printf("%s/n", value.(string))}// Delete element 3m.Delete(3)// Iterate over all the elementsm.Range(func(key, value interface{}) bool {  fmt.Printf("%d: %s/n", key.(int), value.(string))  return true})

Go 在线测试: https://play.golang.org/p/BO8IDVIDwsr4SG28资讯网——每日最新资讯28at.com

onethree1: one2: two

正如你所看到的,Range 方法接受一个 func(key, value interface{}) bool 函数作为参数。如果我们返回 false,则迭代会停止。有趣的是,即使我们在恒定时间之后返回 false(更多信息),最坏情况下的时间复杂度仍然保持为 O(n)。4SG28资讯网——每日最新资讯28at.com

何时应该使用 sync.Map 而不是在经典的 map 上加 sync.Mutex 呢?4SG28资讯网——每日最新资讯28at.com

  • 当我们有频繁读取和不经常写入时(与 sync.RWMutex 类似)
  • 当多个 goroutine 为不相交的键集合读取、写入和覆盖条目。这具体意味着什么?例如,如果我们有一个分片实现,有 4 个 goroutine 每个负责 25% 的键(没有冲突)。在这种情况下,sync.Map 也是首选。

sync.Pool

sync.Pool 是一个并发池,负责安全地保存一组对象。4SG28资讯网——每日最新资讯28at.com

其公共方法包括:4SG28资讯网——每日最新资讯28at.com

  • Get() interface{} 用于检索一个元素
  • Put(interface{}) 用于添加一个元素
pool := &sync.Pool{}pool.Put(NewConnection(1))pool.Put(NewConnection(2))pool.Put(NewConnection(3))connection := pool.Get().(*Connection)fmt.Printf("%d/n", connection.id)connection = pool.Get().(*Connection)fmt.Printf("%d/n", connection.id)connection = pool.Get().(*Connection)fmt.Printf("%d/n", connection.id)
132

值得注意的是,就顺序而言是没有保证的。Get 方法指定它从池中获取一个任意的项目。4SG28资讯网——每日最新资讯28at.com

也可以指定一个创建方法:4SG28资讯网——每日最新资讯28at.com

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

每次调用 Get() 时,它将返回由传递给 pool.New 的函数创建的对象(在本例中是一个指针)。4SG28资讯网——每日最新资讯28at.com

何时应该使用 sync.Pool 呢?有两种情况:4SG28资讯网——每日最新资讯28at.com

  • 第一种情况是当我们需要重用共享且长期存在的对象时,比如一个数据库连接。
  • 第二种情况是优化内存分配。

让我们考虑一个函数的示例,该函数将数据写入缓冲区并将结果持久化到文件中。使用 sync.Pool,我们可以重复使用分配给缓冲区的空间,跨不同的函数调用重复使用同一个对象。4SG28资讯网——每日最新资讯28at.com

第一步是检索先前分配的缓冲区(或者如果是第一次调用,则创建一个,但这已经被抽象化了)。然后,延迟操作是将缓冲区放回池中。4SG28资讯网——每日最新资讯28at.com

func writeFile(pool *sync.Pool, filename string) error {    // Gets a buffer object    buf := pool.Get().(*bytes.Buffer)    // Returns the buffer into the pool    defer pool.Put(buf)    // Reset buffer otherwise it will contain "foo" during the first call    // Then "foofoo" etc.    buf.Reset()    buf.WriteString("foo")    return ioutil.WriteFile(filename, buf.Bytes(), 0644)}

sync.Pool 还有一个要提到的重要点。由于指针可以被放入 Get() 返回的接口值中,无需进行任何分配,因此最好将指针放入池中而不是结构体。4SG28资讯网——每日最新资讯28at.com

这样,我们既可以有效地重用已分配的内存,又可以减轻垃圾收集器的压力,因为如果变量逃逸到堆上,它就不需要再次分配内存。4SG28资讯网——每日最新资讯28at.com

sync.Once

sync.Once 是一个简单而强大的原语,用于确保一个函数只被执行一次。4SG28资讯网——每日最新资讯28at.com

在这个例子中,将只有一个 goroutine 显示输出消息:4SG28资讯网——每日最新资讯28at.com

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

我们使用了 Do(func()) 方法来指定只有这部分代码必须被执行一次。4SG28资讯网——每日最新资讯28at.com

sync.Cond

让我们以最可能最少使用的原语 sync.Cond 结束。4SG28资讯网——每日最新资讯28at.com

它用于向 goroutine 发出信号(一对一)或向 goroutine(s) 广播信号(一对多)。4SG28资讯网——每日最新资讯28at.com

假设我们有一个场景,需要通知一个 goroutine 共享切片的第一个元素已被更新。4SG28资讯网——每日最新资讯28at.com

创建一个 sync.Cond 需要一个 sync.Locker 对象(可以是 sync.Mutex 或 sync.RWMutex):4SG28资讯网——每日最新资讯28at.com

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

接下来,让我们编写一个函数来显示切片的第一个元素:4SG28资讯网——每日最新资讯28at.com

func printFirstElement(s []int, cond *sync.Cond) {    cond.L.Lock()    cond.Wait()    fmt.Printf("%d/n", s[0])    cond.L.Unlock()}

正如你所看到的,我们可以使用 cond.L 来访问内部互斥锁。一旦锁被获取,我们调用 cond.Wait(),它会阻塞直到收到信号。4SG28资讯网——每日最新资讯28at.com

现在回到主 goroutine。我们将通过传递一个共享切片和之前创建的 sync.Cond 来创建一个 printFirstElement 池。然后,我们调用一个 get() 函数,将结果存储在 s[0] 中并发出一个信号:4SG28资讯网——每日最新资讯28at.com

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

这个信号将解除一个创建的 goroutine 的阻塞状态,它将显示 s[0]。4SG28资讯网——每日最新资讯28at.com

然而,如果我们退一步来看,我们可能会认为我们的代码可能违反了 Go 最基本的原则之一:4SG28资讯网——每日最新资讯28at.com

不要通过共享内存来通信;相反,通过通信来共享内存。4SG28资讯网——每日最新资讯28at.com

4SG28资讯网——每日最新资讯28at.com

事实上,在这个例子中,最好使用一个通道来传递 get() 返回的值。4SG28资讯网——每日最新资讯28at.com

然而,我们也提到了 sync.Cond 还可以用于广播信号。4SG28资讯网——每日最新资讯28at.com

让我们修改上一个示例的结尾,将 Signal() 改为 Broadcast():4SG28资讯网——每日最新资讯28at.com

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

在这种情况下,所有的 goroutine 都会被触发。4SG28资讯网——每日最新资讯28at.com

众所周知,通道元素只会被一个 goroutine 捕获。唯一模拟广播的方式是关闭一个通道,但这不能重复使用。因此,尽管 颇具争议,这无疑是一个有趣的特性。4SG28资讯网——每日最新资讯28at.com

还有一个值得提及的 sync.Cond 使用场景,也许是最重要的一个:4SG28资讯网——每日最新资讯28at.com

示例的 Go Playground 地址:https://play.golang.org/p/ap5qXF5DAg54SG28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-34620-0.htmlGo 语言中 sync 包的近距离观察

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 如何使用Python防止他人截取你的屏幕?这里有六种方法!

下一篇: 七个令人瞠目结舌的Python库

标签:
  • 热门焦点
  • MIX Fold3包装盒泄露 新机本月登场

    MIX Fold3包装盒泄露 新机本月登场

    小米的全新折叠屏旗舰MIX Fold3将于本月发布,近日该机的真机包装盒在网上泄露。从图上来看,新的MIX Fold3包装盒在外观设计方面延续了之前的方案,变化不大,这也是目前小米旗舰
  • Mate60手机壳曝光 致敬自己的经典设计

    Mate60手机壳曝光 致敬自己的经典设计

    8月3日消息,今天下午博主数码闲聊站带来了华为Mate60的第三方手机壳图,可以让我们在真机发布之前看看这款华为全新旗舰的大致轮廓。从曝光的图片看,Mate 60背后摄像头面积依然
  • 小米降噪蓝牙耳机Necklace分享:听一首歌 读懂一个故事

    小米降噪蓝牙耳机Necklace分享:听一首歌 读懂一个故事

    在今天下午的小米Civi 2新品发布会上,小米还带来了一款新的降噪蓝牙耳机Necklace,我们也在发布结束的第一时间给大家带来这款耳机的简单分享。现在大家能见到最多的蓝牙耳机
  • 5月安卓手机好评榜:魅族20 Pro夺冠

    5月安卓手机好评榜:魅族20 Pro夺冠

    性能榜和性价比榜之后,我们来看最后的安卓手机好评榜,数据来源安兔兔评测,收集时间2023年5月1日至5月31日,仅限国内市场。第一名:魅族20 Pro好评率:97.50%不得不感慨魅族老品牌还
  • 印度登月最关键一步!月船三号今晚进入环月轨道

    印度登月最关键一步!月船三号今晚进入环月轨道

    8月5日消息,据印度官方消息,月船三号将于北京时间今晚21时30分左右开始近月制动进入环月轨道。这是该探测器能够成功的最关键步骤之一,如果成功将开始围
  • 三言两语说透设计模式的艺术-简单工厂模式

    三言两语说透设计模式的艺术-简单工厂模式

    一、写在前面工厂模式是最常见的一种创建型设计模式,通常说的工厂模式指的是工厂方法模式,是使用频率最高的工厂模式。简单工厂模式又称为静态工厂方法模式,不属于GoF 23种设计
  • 让我们一起聊聊文件的操作

    让我们一起聊聊文件的操作

    文件【1】文件是什么?文件是保存数据的地方,是数据源的一种,比如大家经常使用的word文档、txt文件、excel文件、jpg文件...都是文件。文件最主要的作用就是保存数据,它既可以保
  • 一文掌握 Golang 模糊测试(Fuzz Testing)

    一文掌握 Golang 模糊测试(Fuzz Testing)

    模糊测试(Fuzz Testing)模糊测试(Fuzz Testing)是通过向目标系统提供非预期的输入并监视异常结果来发现软件漏洞的方法。可以用来发现应用程序、操作系统和网络协议等中的漏洞或
  • 支持aptX Lossless无损传输 iQOO TWS 1赛道版发布限时优惠价369元

    支持aptX Lossless无损传输 iQOO TWS 1赛道版发布限时优惠价369元

    2023年7月4日,“无损音质,声动人心”iQOO TWS 1正式发布,支持aptX Lossless无损传输,限时优惠价369元。iQOO TWS 1耳机率先支持端到端aptX Lossless无
Top