Golang 同步原语:sync 包

简介: 资源竞争:多个goroutine同时竞争同一块内存时,就无法知晓谁先访问,结果也无法预料。所以,我们需要确保共享内存资源,只有一个协程执行能够操作。

资源竞争:多个goroutine同时竞争同一块内存时,就无法知晓谁先访问,结果也无法预料。

所以,我们需要确保共享内存资源,只有一个协程执行能够操作。

sync.Mutex

互斥锁:在同一时刻只有一个协程执行某段代码,其他协程都要等待该协程执行完毕后才能执行。

//定义一个同步锁
var mutex sync.Mutex

// 某代码段加锁
mutex.Lock() //加锁
.....
mutex.Unlock()//解锁

被加锁的代码段,可称为临界区。

在同步的程序设计中,临界区段指的是一个访问共享资源的程序片段,而这些共享资源又有无法同时被多个协程访问的特性。某协程在临界区段时,其他协程必须等待,这样可以保证临界区的并发安全。

Mutex的Lock之后,一定会再执行Unlock,彼此时成对存在,所以可以通过使用defer语句,最后释放锁

defer mutex.Unlock()

sync.RWMutex

有些业务场景: 对某变量进行写操作时,这时另一个协程读取该变量时,读取到将是一个过期的值,产生脏读。所以,为解决这种资源竞争的问题,可以使用sync.Mutex来控制,另一个协程必须等写操作完成后,才能进行读操作。

但是,多个协程同时读写竞争资源,也会产生性能问题

读写的三种特殊场景情况分析

  • 写操作同时读操作,可能导致读到脏数据
  • 读操作时同时写操作,产生的结果不可预料
  • 读操作时同时其他协程也在读操作,不会产生并发安全

通过sync.RWMutex读写锁可以有效的提升性能,多个协程可以同时读操作,不用互相等待

var mutex sync.RWMutex

mutex.RLock()
defer mutex.RUnlock()

sync.WaitGroup

如果在一个主函数main()中,启动了100个协程,在100个协程还未执行完成时,主函数就提前返回了,将导致什么情况?

主函数返回了,则整个程序也就退出。这将可能出现子协程没有执行完毕,产生不可预知的结果。为解决该情况,我们应当保证所有协程都执行完毕后,在退出程序。

sync.WaitGroup就是来解决这种情况。

使用方式:

  • 声明一个sync.WaitGroup,然后通过Add方法设置计数器的值,多少个协程就设置多少
  • 每个协程结束后调用Done方法,让计数器减1
  • 最后调用Wait方法一直等待,直到计数器值为0时,表示所有协程都执行完毕
var wg sync.WaitGroup

go func(){
    defer wg.Done() //一个协程结束后计数器减1
    ....
}
...

// 一直等待直到计数器为0
wg.Wait()

使用场景:通过多个协程共同做一件事,可以有效提高效率。比如,下载文件时,分成多个协程去下载,只有所有协程都下载完成整个文件才算下载完成。

sync.Once

需求场景:让代码只执行一次

  • 创建某个对象的单例
  • 只加载一次的资源

Go语言自带的一个示例

1. func main() {
2.    doOnce()
3. }
4. func doOnce() {
5.    var once sync.Once
6.    onceBody := func() {
7.       fmt.Println("Only once")
8.    }
9.    //用于等待协程执行完毕
10.    done := make(chan bool)
11.    //启动10个协程执行once.Do(onceBody)
12.    for i := 0; i < 10; i++ {
13.       go func() {
14.          //把要执行的函数(方法)作为参数传给once.Do方法即可
15.          once.Do(onceBody)
16.          done <- true
17.       }()
18.    }
19.    for i := 0; i < 10; i++ {
20.       <-done
21.    }
22. }

启动10个协程执行onceBody,但使用once.Do()方法,所以函数onceBody只被执行一次

sync.NewCond

具有阻塞协程和唤醒协程的功能。

var cond = sync.NewCond(&sync.Mutex)
  • Wait()方法,阻塞当前协程,直到被其他协程调用Broadcase或Signal方法唤醒,使用时必须加锁
  • Signal,唤醒一个等待时间最长的协程
  • Broadcast 唤醒所有等待的协程

注:调用Signal和Broadcast前,要保证目标协程处于Wait阻塞状态

sync.Map

  1. Store:存储一对 key-value 值。
  2. Load:根据 key 获取对应的 value 值,并且可以判断 key 是否存在。
  3. LoadOrStore:如果 key 对应的 value 存在,则返回该 value;如果不存在,存储相应的 value。
  4. Delete:删除一个 key-value 键值对。
  5. Range:循环迭代 sync.Map,效果与 for range 一样
目录
相关文章
|
3月前
|
Go
Golang的math包常用方法
这篇文章介绍了Golang的math包中的常量和常用方法,并通过示例代码展示了如何使用这些常量和方法。
186 87
Golang的math包常用方法
|
1月前
|
Go 计算机视觉
在Golang高并发环境中如何进行协程同步?
在此示例中,使用互斥锁来保护对共享计数器变量 c 的访问,确保并发的 HTTP 请求不会产生数据竞争。
46 3
|
3月前
|
存储 Go
Golang语言基于go module方式管理包(package)
这篇文章详细介绍了Golang语言中基于go module方式管理包(package)的方法,包括Go Modules的发展历史、go module的介绍、常用命令和操作步骤,并通过代码示例展示了如何初始化项目、引入第三方包、组织代码结构以及运行测试。
72 3
|
3月前
|
Go
Golang语言基于GOPATH方式管理包(package)
这篇文章详细介绍了Golang语言中基于GOPATH方式管理包(package)的方法,包括包的概述、定义、引入格式、别名使用、匿名引入,以及如何快速入门自定义包,并通过具体代码案例展示了包的环境准备、代码编写、细节说明和程序运行。
46 3
|
3月前
|
Go
Golang语言之包依赖管理
这篇文章详细介绍了Go语言的包依赖管理工具,包括godep和go module的使用,以及如何在项目中使用go module进行依赖管理,还探讨了如何导入本地包和第三方库下载的软件包存放位置。
46 3
|
4月前
|
机器学习/深度学习 存储 人工智能
Golang bytes 包学习
Golang bytes 包学习
33 3
|
4月前
|
Go 开发者
|
4月前
|
存储 测试技术 Go
Golang 包:构建模块化代码的基石
【8月更文挑战第31天】
53 0
|
6月前
|
SQL NoSQL Go
技术经验分享:Golang标准库:errors包应用
技术经验分享:Golang标准库:errors包应用
47 0
|
6月前
|
移动开发 Go
golang bufio包怎么用?
`bufio` 是 Go 语言中用于提高 I/O 性能的包,它通过使用缓冲区减少对低效磁盘 I/O 操作的调用。简而言之,`bufio` 提供带缓冲的读写功能,减少读取或写入文件时的系统调用次数,从而提升程序性能。