当前位置: 首页 > news >正文

golang学习笔记——互斥锁sync.Mutex、计数器sync.WaitGroup、读写锁sync.RWMutex

文章目录

  • 互斥锁: sync.Mutex
  • sync.WaitGroup 计数器
    • 例子
    • func (*WaitGroup) Add
    • func (*WaitGroup) Done
    • func (*WaitGroup) Wait
  • 读写互斥锁
  • 参考资料

临界区总是需要通过同步机制进行保护的,否则就会产生竞态条件,导致数据不一致。

互斥锁: sync.Mutex

一个互斥锁可以被用来保护一个临界区,我们可以通过它来保证在同一时刻只有一个 goroutine 处于该临界区之内(同一个时刻只有一个线程能够拿到锁)

先通过一个并发读写的例子演示一下,当多线程同时访问全局变量时,结果会怎样?

package mainimport ("fmt"
)var count intfunc main() {for i := 0; i < 2; i++ {go func() {for i := 1000000; i > 0; i-- {count++}fmt.Println(count)}()}fmt.Scanf("\n") //等待子线程全部结束
}//运行结果
//1003065
//1033207

修改代码,在累加的地方添加互斥锁,就能保证我们每次得到的结果都是想要的值

package mainimport ("fmt""sync"
)var (count intlock  sync.Mutex
)func main() {for i := 0; i < 2; i++ {go func() {for i := 1000000; i > 0; i-- {lock.Lock()count++lock.Unlock()}fmt.Println(count)}()}fmt.Scanf("\n") //等待子线程全部结束
}// 运行结果
//1991307
//2000000

每当有 goroutine 想进入临界区时,都需要先对它进行锁定,并且,每个 goroutine 离开临界区时,都要及时地对它进行解锁,锁定和解锁操作分别通过互斥锁 sync.Mutex 的 Lock 和 Unlock 方法实现。使用互斥锁的时候有以下注意事项:

  • 不要重复锁定互斥锁;
  • 不要忘记解锁互斥锁,必要时使用 defer 语句;
  • 不要对尚未锁定或者已解锁的互斥锁解锁;
  • 不要在多个函数之间直接传递互斥锁。

sync.WaitGroup 计数器

type WaitGroup struct {// contains filtered or unexported fields// 包含已筛选或未导出的字段
}

A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls Add to set the number of goroutines to wait for. Then each of the goroutines runs and calls Done when finished. At the same time, Wait can be used to block until all goroutines have finished.

WaitGroup等待goroutines的集合完成。主goroutine调用Add来设置要等待的goroutine的数量。然后,每个goroutine都会运行,并在完成时调用Done。同时,可以使用Wait来阻止,直到所有goroutine都完成。

A WaitGroup must not be copied after first use.
首次使用后不得复制WaitGroup。

In the terminology of the Go memory model, a call to Done “synchronizes before” the return of any Wait call that it unblocks.
在Go内存模型的术语中,对Done的调用在其取消阻止的任何Wait调用返回之前“同步”。

例子

This example fetches several URLs concurrently, using a WaitGroup to block until all the fetches are complete.
此示例同时获取多个URL,使用WaitGroup进行阻止,直到所有获取完成。

package mainimport ("sync"
)type httpPkg struct{}func (httpPkg) Get(url string) {}var http httpPkgfunc main() {var wg sync.WaitGroupvar urls = []string{"http://www.csdn.net/","http://www.youku.com/","http://www.baidu.com/",}for _, url := range urls {// Increment the WaitGroup counter.// 增加WaitGroup计数器。wg.Add(1)// Launch a goroutine to fetch the URL.// 启动goroutine以获取URL。go func(url string) {// Decrement the counter when the goroutine completes.// goroutine完成时递减计数器。defer wg.Done()// Fetch the URL.// 获取URL。http.Get(url)}(url)}// Wait for all HTTP fetches to complete.// 等待所有HTTP获取完成。wg.Wait()
}

func (*WaitGroup) Add

func (wg *WaitGroup) Add(delta int)

Add adds delta, which may be negative, to the WaitGroup counter. If the counter becomes zero, all goroutines blocked on Wait are released. If the counter goes negative, Add panics.

Add向WaitGroup计数器添加可能为负数的delta。如果计数器变为零,则会释放在Wait上阻止的所有goroutines。如果计数器为负数,Add会恐慌。

Note that calls with a positive delta that occur when the counter is zero must happen before a Wait. Calls with a negative delta, or calls with a positive delta that start when the counter is greater than zero, may happen at any time.

请注意,计数器为零时发生的具有正增量的调用必须在等待之前发生。任何时候都可能发生具有负增量的调用,或当计数器大于零时开始的具有正增量的调用。

Typically this means the calls to Add should execute before the statement creating the goroutine or other event to be waited for. If a WaitGroup is reused to wait for several independent sets of events, new Add calls must happen after all previous Wait calls have returned. See the WaitGroup example.

通常,这意味着对Add的调用应该在创建goroutine或其他待等待事件的语句之前执行。如果重用一个WaitGroup来等待多个独立的事件集,则必须在所有以前的wait调用都返回后进行新的Add调用。请参阅WaitGroup示例。

func (*WaitGroup) Done

func (wg *WaitGroup) Done()

Done decrements the WaitGroup counter by one.
Done将WaitGroup计数器递减一。

func (*WaitGroup) Wait

func (wg *WaitGroup) Wait()

Wait blocks until the WaitGroup counter is zero.
等待块,直到WaitGroup计数器为零。

读写互斥锁

互斥锁是完全互斥的,但是有很多实际的场景下是读多写少的,当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的,这种场景下使用读写锁是更好的一种选择。读写锁在Go语言中使用sync包中的RWMutex类型。

读写锁分为两种:读锁和写锁。当一个goroutine获取读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待。

读写锁示例:

package mainimport ("fmt""sync""time"
)var (x      int64wg     sync.WaitGrouplock   sync.Mutexrwlock sync.RWMutex
)func write() {// lock.Lock()   // 加互斥锁rwlock.Lock() // 加写锁x = x + 1time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒rwlock.Unlock()                   // 解写锁// lock.Unlock()                     // 解互斥锁wg.Done()
}func read() {// lock.Lock()                  // 加互斥锁rwlock.RLock()               // 加读锁time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒rwlock.RUnlock()             // 解读锁// lock.Unlock()                // 解互斥锁wg.Done()
}func main() {start := time.Now()for i := 0; i < 10; i++ {wg.Add(1)go write()}for i := 0; i < 1000; i++ {wg.Add(1)go read()}wg.Wait()end := time.Now()fmt.Println(end.Sub(start))
}// 173.3553ms

参考资料

sync包
golang并发之sync包

http://www.lryc.cn/news/259992.html

相关文章:

  • MFC 加载本地文件设置图标
  • 飞天使-linux操作的一些技巧与知识点6-ansible结合jinja2使用,可规范化进行自动化管控
  • ROS2 Control分析讲解
  • Java TCP(一对一)聊天简易版
  • 2.4 C语言之运算符
  • 做题笔记:SQL Sever 方式做牛客SQL的题目--SQL157
  • 微信小程序map视野发生改变时切换定位点
  • javaweb搭配ajax和json
  • VS2022 将项目打包,导出为exe运行
  • 【Py/Java/C++三种语言OD2023C卷真题】20天拿下华为OD笔试【DP】2023C-分班【欧弟算法】全网注释最详细分类最全的华为OD真题题解
  • pr模板哪个网站好?免费Pr模板视频素材下载网站 Prmuban.com
  • 【论文阅读】LoRA: Low-Rank Adaptation of Large Language Models
  • MybatisPlus的分页插件
  • 保障网络安全:了解威胁检测和风险评分的重要性
  • 3D摄影棚布光:Set A Light 3D Studio
  • #HarmonyOS:应用的包名配置--应用图标和标签配置--配置链接
  • docker小白第三天
  • FFmpegd的AVBSF
  • 深圳三男子写字楼内吸烟被罚,快用富维AI神器,实时监控防火灾
  • 有哪些已经上线的vue商城项目?
  • Nginx服务器配置SSL证书
  • 【JVM从入门到实战】(六)类加载器的双亲委派机制
  • SpringCloud面试题及答案(最新50道大厂版,持续更新)
  • Next.js 的设计理念
  • 26、文件包含
  • 飞天使-linux操作的一些技巧与知识点4-ansible常用的技巧,配置等
  • Stable Diffusion 源码解析(1)
  • java.lang.ClassNotFoundException:javax.xml.bind.DatatypeConverter 报错解决
  • Python学习笔记-类
  • 了解如何在linux使用podman管理容器