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

本文主要是介绍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包

这篇关于golang学习笔记——互斥锁sync.Mutex、计数器sync.WaitGroup、读写锁sync.RWMutex的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/494465

相关文章

重新对Java的类加载器的学习方式

《重新对Java的类加载器的学习方式》:本文主要介绍重新对Java的类加载器的学习方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍1.1、简介1.2、符号引用和直接引用1、符号引用2、直接引用3、符号转直接的过程2、加载流程3、类加载的分类3.1、显示

Golang HashMap实现原理解析

《GolangHashMap实现原理解析》HashMap是一种基于哈希表实现的键值对存储结构,它通过哈希函数将键映射到数组的索引位置,支持高效的插入、查找和删除操作,:本文主要介绍GolangH... 目录HashMap是一种基于哈希表实现的键值对存储结构,它通过哈希函数将键映射到数组的索引位置,支持

Java学习手册之Filter和Listener使用方法

《Java学习手册之Filter和Listener使用方法》:本文主要介绍Java学习手册之Filter和Listener使用方法的相关资料,Filter是一种拦截器,可以在请求到达Servl... 目录一、Filter(过滤器)1. Filter 的工作原理2. Filter 的配置与使用二、Listen

利用Python快速搭建Markdown笔记发布系统

《利用Python快速搭建Markdown笔记发布系统》这篇文章主要为大家详细介绍了使用Python生态的成熟工具,在30分钟内搭建一个支持Markdown渲染、分类标签、全文搜索的私有化知识发布系统... 目录引言:为什么要自建知识博客一、技术选型:极简主义开发栈二、系统架构设计三、核心代码实现(分步解析

golang获取当前时间、时间戳和时间字符串及它们之间的相互转换方法

《golang获取当前时间、时间戳和时间字符串及它们之间的相互转换方法》:本文主要介绍golang获取当前时间、时间戳和时间字符串及它们之间的相互转换,本文通过实例代码给大家介绍的非常详细,感兴趣... 目录1、获取当前时间2、获取当前时间戳3、获取当前时间的字符串格式4、它们之间的相互转化上篇文章给大家介

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

golang 日志log与logrus示例详解

《golang日志log与logrus示例详解》log是Go语言标准库中一个简单的日志库,本文给大家介绍golang日志log与logrus示例详解,感兴趣的朋友一起看看吧... 目录一、Go 标准库 log 详解1. 功能特点2. 常用函数3. 示例代码4. 优势和局限二、第三方库 logrus 详解1.

Redis中高并发读写性能的深度解析与优化

《Redis中高并发读写性能的深度解析与优化》Redis作为一款高性能的内存数据库,广泛应用于缓存、消息队列、实时统计等场景,本文将深入探讨Redis的读写并发能力,感兴趣的小伙伴可以了解下... 目录引言一、Redis 并发能力概述1.1 Redis 的读写性能1.2 影响 Redis 并发能力的因素二、

Java进阶学习之如何开启远程调式

《Java进阶学习之如何开启远程调式》Java开发中的远程调试是一项至关重要的技能,特别是在处理生产环境的问题或者协作开发时,:本文主要介绍Java进阶学习之如何开启远程调式的相关资料,需要的朋友... 目录概述Java远程调试的开启与底层原理开启Java远程调试底层原理JVM参数总结&nbsMbKKXJx

Golang中拼接字符串的6种方式性能对比

《Golang中拼接字符串的6种方式性能对比》golang的string类型是不可修改的,对于拼接字符串来说,本质上还是创建一个新的对象将数据放进去,主要有6种拼接方式,下面小编就来为大家详细讲讲吧... 目录拼接方式介绍性能对比测试代码测试结果源码分析golang的string类型是不可修改的,对于拼接字