介绍sync包中常用的方法,

- sync:提供基本的同步原语(比如Mutex、RWMutex、Locker)和 工具类(Once、WaitGroup、Cond、Pool、Map)
- sync/atomic:提供变量的原子操作(基于硬件指令 compare-and-swap)

[Mutex] 互斥锁

Mutex 也称为互斥锁,互斥锁就是互相排斥的锁,它可以用作保护临界区的共享资源,保证同一时刻只有一个 goroutine 操作临界区中的共享资源。互斥锁 Mutex 类型有两个方法,LockUnlock

使用互斥锁的注意事项:

  • Mutex 类型变量的零值是一个未锁定状态的互斥锁。
  • Mutex 在首次被使用之后就不能再被拷贝(Mutex 是值类型,拷贝会同时拷贝互斥锁的状态)。
  • Mutex 在未锁定状态(还未锁定或已被解锁),调用 Unlock 方法,将会引发运行时错误。
  • Mutex 的锁定状态与特定 goroutine 没有关联,Mutex 被一个 goroutine 锁定, 可以被另外一个 goroutine 解锁。(不建议使用,必须使用时需要格外小心。)
  • Mutex 的 Lock 方法和 Unlock 方法要成对使用,不要忘记将锁定的互斥锁解锁,一般做法是使用 defer。

源码:

type Mutex struct {
state int32 // 互斥锁的状态
sema uint32 // 信号量,用于控制互斥锁的状态
}
# 求和运算

func mutexAdd() {
var total int32 // 使用多协程处理
var wg sync.WaitGroup
var lock sync.Mutex for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done() // 不加锁的情况下,会出现total不为100的时候
lock.Lock()
defer lock.Unlock() // 防止忘记解锁
total += 1
//atomic.AddInt32(&total, 1) // 无需加锁即可
// lock.Unlock()
}()
}
wg.Wait()
fmt.Println("total:", total)
}

[RWLock] 读写锁

RWMutex 也称为读写互斥锁,读写互斥锁就是读取/写入互相排斥的锁。它可以由任意数量的读取操作的 goroutine 或单个写入操作的 goroutine 持有。读写互斥锁 RWMutex 类型有五个方法,LockUnlockRlockRUnlockRLocker。其中,RLocker 返回一个 Locker 接口,该接口通过调用 rw.RLockrw.RUnlock 来实现 Lock 和 Unlock 方法。

使用读写互斥锁的注意事项:

  • RWMutex 类型变量的零值是一个未锁定状态的互斥锁。
  • RWMutex 在首次被使用之后就不能再被拷贝。
  • RWMutex 的读锁或写锁在未锁定状态,解锁操作都会引发 panic。
  • RWMutex 的一个写锁 Lock 去锁定临界区的共享资源,如果临界区的共享资源已被(读锁或写锁)锁定,这个写锁操作的 goroutine 将被阻塞直到解锁。
  • RWMutex 的读锁不要用于递归调用,比较容易产生死锁。
  • RWMutex 的锁定状态与特定的 goroutine 没有关联。一个 goroutine 可以 RLock(Lock),另一个 goroutine 可以 RUnlock(Unlock)。
  • 写锁被解锁后,所有因操作锁定读锁而被阻塞的 goroutine 会被唤醒,并都可以成功锁定读锁。
  • 读锁被解锁后,在没有被其他读锁锁定的前提下,所有因操作锁定写锁而被阻塞的 goroutine,其中等待时间最长的一个 goroutine 会被唤醒。

读写锁的访问控制规则如下:
① 多个写操作之间是互斥的

② 写操作与读操作之间也是互斥的

③ 多个读操作之间不是互斥的

源码:

type RWMutex struct {
w Mutex // held if there are pending writers
writerSem uint32 // semaphore for writers to wait for completing readers
readerSem uint32 // semaphore for readers to wait for completing writers
readerCount int32 // number of pending readers
readerWait int32 // number of departing readers
}

使用示例:

package main

import "sync"

type Cache struct {
data map[string]any
rwMutex sync.RWMutex
} func NewCache() *Cache {
return &Cache{
data: make(map[string]any),
}
} func (c *Cache) GetValue(key string) (any, bool) {
c.rwMutex.RLock()
defer c.rwMutex.RUnlock()
val, isOk := c.data[key]
return val, isOk
} func (c *Cache) SetValue(key string, val any) {
c.rwMutex.Lock() // 对于写入操作使用互斥锁
defer c.rwMutex.Unlock()
c.data[key] = val
}

参考文档: https://juejin.cn/post/7218554163051413561

[Once]

sync.Once 被用于控制变量的初始化,这个变量的读写通常遵循单例模式,满足这三个条件:

  1. 当且仅当第一次读某个变量时,进行初始化(写操作)
  2. 变量被初始化过程中,所有读都被阻塞(读操作;当变量初始化完成后,读操作继续进行
  3. 变量仅初始化一次,初始化完成后驻留在内存里

once.Sync 可用于任何符合 "exactly once" 语义的场景,比如:

  1. 初始化 rpc/http client
  2. open/close 文件
  3. close channel
  4. 线程池初始化

[WaitGroup]

一个 WaitGroup 对象可以等待一组协程结束。使用方法是:

  1. main协程通过调用 wg.Add(delta int) 设置worker协程的个数,然后创建worker协程;
  2. worker协程执行结束以后,都要调用 wg.Done()
  3. main协程调用 wg.Wait() 且被block,直到所有worker协程全部执行结束后返回。

实例:

 func  main() {
// 省略部分代码 ...
var wg sync.WaitGroup
for _, task := range tasks {
task := task
wg.Add(1)
go func() {
task()
defer wg.Done()
}()
}
wg.Wait()
// 省略部分代码...
}

说明:

1.wg.Done 必须在wg.Add之后执行

2.wg.Done在worker协程中调用, 保证调用一次,不能因为panic或者其他原因导致没有执行(建议使用defer wg.Done)

3.task := task 需要进行再次赋值,否则会读取到最后一个元素的值(

  1. for-loop 内创建的局部变量,即便名字相同,内存地址也不会复用

)

适用场景:

1.需要向多个服务请求数据,并最终将这些服务请求结果进行最终整合返回给前端进行展示

基于WaitGroup的使用,发现一个协程出现错误的时候,并不会将子协程的错误抛送给主goroutine :   golang.org/x/sync/errgroup

使用示例:

package main

 import (
"fmt"
"net/http" "golang.org/x/sync/errgroup"
) func main() {
var urls = []string{
"http://www.golang.org/",
"http://www.baidu.com/",
"http://www.bokeyuan12111.com/",
}
g := new(errgroup.Group)
for _, url := range urls {
url := url
g.Go(func() error {
resp, err := http.Get(url)
if err != nil {
fmt.Println(err)
return err
}
fmt.Printf("get [%s] success: [%d] \n", url, resp.StatusCode)
return resp.Body.Close()
})
}
if err := g.Wait(); err != nil {
fmt.Println(err)
} else {
fmt.Println("All success!")
}
}

输出:

get [http://www.baidu.com/] success: [200]
Get "http://www.bokeyuan12111.com/": dial tcp: lookup www.bokeyuan12111.com: no such host
Get "http://www.golang.org/": dial tcp 142.251.42.241:80: i/o timeout
Get "http://www.bokeyuan12111.com/": dial tcp: lookup www.bokeyuan12111.com: no such host

可以看到,执行获取www.bokeyuan12111.com和www.golang.org两个 url 的子 groutine 均发生了错误,在主任务 goroutine 中成功捕获到了第一个错误信息。

除了 拥有 WaitGroup 的控制能力 和 错误传播 的功能之外,errgroup 还有最重要的 context 反向传播机制

带有上下文取消的使用方式:

package main

import (
"context"
"fmt" "golang.org/x/sync/errgroup"
) func main() { g, ctx := errgroup.WithContext(context.Background())
dataChan := make(chan int, 20) // 数据生产端任务子 goroutine
g.Go(func() error {
defer close(dataChan)
for i := 1; ; i++ {
if i == 10 {
return fmt.Errorf("data 10 is wrong")
}
dataChan <- i
fmt.Println(fmt.Sprintf("sending %d", i))
}
}) // 数据消费端任务子 goroutine
for i := 0; i < 3; i++ {
g.Go(func() error {
for j := 1; ; j++ {
select {
case <-ctx.Done():
return ctx.Err()
case number := <-dataChan:
fmt.Println(fmt.Sprintf("receiving %d", number))
}
}
})
} // 主任务 goroutine 等待 pipeline 结束数据流
err := g.Wait()
if err != nil {
fmt.Println(err)
}
fmt.Println("main goroutine done!")
}

在以上示例中,我们模拟了一个数据传送管道。在数据的生产与消费任务集中,有四个子任务 goroutine:一个生产数据的 goroutine,三个消费数据的 goroutine。当数据生产方存在错误数据时(数据等于 10 ),我们停止数据的生产与消费,并将错误抛出,回到 main goroutine 的执行逻辑中。

可以看到,因为 errgroup 中的 Context cancle 函数的嵌入,我们在子任务 goroutine 中也能反向控制任务上下文。

程序的某一次运行,输出结果如下:

sending 1
sending 2
sending 3
sending 4
sending 5
sending 6
sending 7
sending 8
sending 9
receiving 1
receiving 3
receiving 2
receiving 4
data 10 is wrong
main goroutine done!

  

[Cond]

[Pool]

[Map]

[sync/atomic] 原子相关操作

golang之sync包的更多相关文章

  1. golang 中 sync包的 WaitGroup

    golang 中的 sync 包有一个很有用的功能,就是 WaitGroup 先说说 WaitGroup 的用途:它能够一直等到所有的 goroutine 执行完成,并且阻塞主线程的执行,直到所有的 ...

  2. Golang学习 - sync 包

    ------------------------------------------------------------ 临时对象池 Pool 用于存储临时对象,它将使用完毕的对象存入对象池中,在需要 ...

  3. golang的sync包例子

    package main import ( "fmt" "sync" ) var wg sync.WaitGroup func asyncTestFunc() ...

  4. golang中sync.RWMutex和sync.Mutex区别

    golang中sync包实现了两种锁Mutex (互斥锁)和RWMutex(读写锁),其中RWMutex是基于Mutex实现的,只读锁的实现使用类似引用计数器的功能. type Mutex     f ...

  5. go语言中sync包和channel机制

    文章转载至:https://www.bytelang.com/article/content/A4jMIFmobcA= golang中实现并发非常简单,只需在需要并发的函数前面添加关键字"Go&quo ...

  6. Golang官方log包详解

    Golang官方log包详解 以下全是代码, 详解在注释中, 请从头到尾看 // Copyright 2009 The Go Authors. All rights reserved. // Use ...

  7. Golang的sync.WaitGroup 实现逻辑和源码解析

    在Golang中,WaitGroup主要用来做go Routine的等待,当启动多个go程序,通过waitgroup可以等待所有go程序结束后再执行后面的代码逻辑,比如: func Main() { ...

  8. 从0写一个Golang日志处理包

    WHY 日志概述 日志几乎是每个实际的软件项目从开发到最后实际运行过程中都必不可少的东西.它对于查看代码运行流程,记录发生的事情等方面都是很重要的. 一个好的日志系统应当能准确地记录需要记录的信息,同 ...

  9. Golang爬虫示例包系列教程(一):pedaily.com投资界爬虫

    Golang爬虫示例包 文件结构 自己用Golang原生包封装了一个爬虫库,源码见go get -u -v github.com/hunterhug/go_tool/spider ---- data ...

  10. 一键解决 go get golang.org/x 包失败

    问题描述 当我们使用 go get.go install.go mod 等命令时,会自动下载相应的包或依赖包.但由于众所周知的原因,类似于 golang.org/x/... 的包会出现下载失败的情况. ...

随机推荐

  1. 无法加载nodejs\vue.ps1

    发现问题 刚换了电脑之后,安装了node.js.vue/cli,在vscode中使用vue ui命令新建vue项目时,发现报错如下: 分析问题 多番查询后发现,一般此类问题大多出现在第一次运行脚本的电 ...

  2. 用Python实现阿拉伯数字转换成中国汉字

    要将阿拉伯数字转换成中国汉字表示的数字,我们需要一个映射表来转换每个数字,并且处理不同位数的数字(如十.百.千.万等). 1. Python实现阿拉伯数字转换成中国汉字 下面是一个完整的Python代 ...

  3. SpringMVC —— 响应

    响应页面    响应文本数据    响应json数据    响应json集合数据    注解      转换json时使用了类型转换器     

  4. 2024-09-21:用go语言,给定一个字符串 s,字符串中的每个字符要么是小写字母,要么是问号‘?‘。对于一个仅包含小写字母的字符串t,我们定义cost(i)为在t的前i个字符中与t[i]相同的字

    2024-09-21:用go语言,给定一个字符串 s,字符串中的每个字符要么是小写字母,要么是问号'?'.对于一个仅包含小写字母的字符串t,我们定义cost(i)为在t的前i个字符中与t[i]相同的字 ...

  5. 以后基于 Topass 的博客加密方法通告

    Topass 加密方法 以后会将部分未公开内容公开,请你通过此加密途径来破解密码 特别地,为了保证博客的浏览体验,我不会通过这种方法加密任何一种应该公开的文章 话说你们不妨猜猜用的什么算法

  6. Maya 无法选中坐标轴 的 解决办法

    事件起因: 有项目组某同事在使用maya时,无法选中坐标轴,导致在拖动东西的时候总是无法对准坐标轴线. 解决办法: maya软件中设置: Windows -> Settings/Preferen ...

  7. CentOS7 控制台上安装运行 vmware workstation 备忘录

    目标平台 CentOS 7.5.1804 无桌面,要跑个 Ubuntu.vmx 1.安装依赖库 yum -y install perl gcc kernel-devel libX11 libXiner ...

  8. ARMv8 寄存器

    本文主要介绍 Armv8/v9 指令集架构中常用部分,详细的还是要看 Arm architecture reference manual. ARMv8 架构 ARMv8 架构支持3种指令集: T32, ...

  9. linux cpufreq framework(5)_ARM big Little driver

    1. 前言 也许大家会觉得奇怪:为什么Linux kernel把对ARM big·Lttile的支持放到了cpufreq的框架中? 众所周知,ARM的big·Little架构,也称作HMP(具体可参考 ...

  10. 墨天轮国产数据库沙龙 | 张玮绚:TDengine,高性能、分布式、支持SQL的时序数据库

    分享嘉宾:张玮绚(Wade Zhang)北京涛思数据科技有限公司(TDengine)研发VP 整理:墨天轮 导读 TDengine 是一款高性能.分布式.支持 SQL 的时序数据库,让大量设备.数据采 ...