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 一样
目录
相关文章
|
6天前
|
Go
Golang标准库sync的使用
Golang标准库sync的使用
12 2
|
6天前
|
监控 Go 开发者
Golang深入浅出之-Goroutine泄漏检测与避免:pprof与debug包
【5月更文挑战第2天】本文介绍了Go语言并发编程中可能遇到的Goroutine泄漏问题,以及如何使用`pprof`和`debug`包来检测和防止这种泄漏。常见的问题包括忘记关闭channel和无限制创建goroutine。检测方法包括启动pprof服务器以监控Goroutine数量,使用`debug.Stack()`检查堆栈,以及确保每个Goroutine有明确的结束条件。通过这些手段,开发者可以有效管理Goroutine,维持程序性能。
41 7
|
6天前
|
Java Go
Golang深入浅出之-Goroutine泄漏检测与避免:pprof与debug包
【5月更文挑战第1天】本文介绍了Go语言中goroutine泄漏的问题及其影响,列举了忘记关闭通道、无限循环和依赖外部条件等常见泄漏原因。通过引入`net/http/pprof`和`runtime/debug`包,可以检测和避免goroutine泄漏。使用pprof的HTTP服务器查看goroutine堆栈,利用`debug`包的`SetGCPercent`和`FreeOSMemory`函数管理内存。实践中,应使用`sync.WaitGroup`、避免无限循环和及时关闭通道来防止泄漏。理解这些工具和策略对维护Go程序的稳定性至关重要。
26 4
|
6天前
|
安全 测试技术 Go
Golang深入浅出之-Go语言单元测试与基准测试:testing包详解
【4月更文挑战第27天】Go语言的`testing`包是单元测试和基准测试的核心,简化了测试流程并鼓励编写高质量测试代码。本文介绍了测试文件命名规范、常用断言方法,以及如何进行基准测试。同时,讨论了测试中常见的问题,如状态干扰、并发同步、依赖外部服务和测试覆盖率低,并提出了相应的避免策略,包括使用`t.Cleanup`、`t.Parallel()`、模拟对象和检查覆盖率。良好的测试实践能提升代码质量和项目稳定性。
18 1
|
6天前
|
安全 Go
Golang深入浅出之-Go语言标准库中的文件读写:io/ioutil包
【4月更文挑战第27天】Go语言的`io/ioutil`包提供简单文件读写,适合小文件操作。本文聚焦`ReadFile`和`WriteFile`函数,讨论错误处理、文件权限、大文件处理和编码问题。避免错误的关键在于检查错误、设置合适权限、采用流式读写及处理编码。遵循这些最佳实践能提升代码稳定性。
24 0
|
6天前
|
Go API 开发者
Golang深入浅出之-文件与目录操作:os与path/filepath包
【4月更文挑战第26天】Go语言标准库`os`和`path/filepath`提供文件读写、目录操作等功能。本文涵盖`os.Open`, `os.Create`, `os.Mkdir`, `filepath.Join`等API的使用,强调了文件关闭、路径处理、并发写入和权限问题的处理,并给出实战代码示例,帮助开发者高效、安全地操作文件与目录。注意使用`defer`关闭文件,`filepath`处理路径分隔符,以及通过同步机制解决并发写入冲突。
27 2
|
6天前
|
安全 Unix Go
Golang深入浅出之-Go语言中的时间与日期处理:time包详解
【4月更文挑战第26天】Go语言的`time`包提供处理日期和时间的功能,包括`time.Time`类型、时间戳、格式化与解析。本文讨论了核心概念、常见问题(如时区处理、格式字符串混淆、超时控制和并发安全)及解决方法。推荐使用`time.LoadLocation`管理时区,熟悉时间格式规则,用`context`精确控制超时,并注意并发安全。文中通过代码示例展示了如何获取格式化时间、计算时间差以及创建定时任务。学习和应用这些知识可提高程序的健壮性和准确性。
31 2
|
6天前
|
XML JSON Go
Golang深入浅出之-XML处理在Go语言中的实现:encoding/xml包
【4月更文挑战第26天】Go语言的`encoding/xml`库提供XML处理,包括序列化和反序列化。本文讨论了XML处理的基础,如`xml.Marshal`和`xml.Unmarshal`函数,以及常见问题和易错点,如标签命名、结构体嵌套、omitempty标签和命名空间。建议遵循标签命名规则,正确处理嵌套和属性,谨慎使用omitempty,以及理解并有效利用命名空间。文中还给出了基础示例和处理XML属性的代码示例,帮助读者掌握XML处理技巧。
24 1
Golang深入浅出之-XML处理在Go语言中的实现:encoding/xml包
|
6天前
|
JSON 编解码 Go
Golang深入浅出之-HTTP客户端编程:使用net/http包发起请求
【4月更文挑战第25天】Go语言`net/http`包提供HTTP客户端和服务器功能,简化高性能网络应用开发。本文探讨如何发起HTTP请求,常见问题及解决策略。示例展示GET和POST请求的实现。注意响应体关闭、错误处理、内容类型设置、超时管理和并发控制。最佳实践包括重用`http.Client`,使用`context.Context`,处理JSON以及记录错误日志。通过实践这些技巧,提升HTTP编程技能。
26 1
|
6天前
|
数据管理 Go 开发者
Golang深入浅出之-Go语言上下文(context)包:处理取消与超时
【4月更文挑战第25天】Go语言中的`context`包在并发、网络请求和长任务中至关重要,提供取消、截止时间和元数据管理。本文探讨`context`基础,如`Background()`、`TODO()`、`WithCancel()`、`WithDeadline()`和`WithTimeout()`。常见问题包括不当传递、过度使用`Background()`和`TODO()`以及忽略错误处理。通过取消和超时示例,强调正确传递上下文、处理取消错误和设置超时以提高应用健壮性和响应性。正确使用`context`是构建稳定高效Go应用的关键。
22 1