Golang语言goroutine协程并发安全及锁机制

简介: 这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。

                                              作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。

一.多协程操作同一数据问题引出

package main import ( "fmt" "sync" ) var ( count int wg sync.WaitGroup ) func add() { defer wg.Done() for i := 1; i <= 1000000; i++ { count++ } } func sub() { defer wg.Done() for i := 1; i <= 1000000; i++ { count-- } } func main() { // 开启2个协程等待 wg.Add(2) go add() go sub() wg.Wait() // 在理论上,这个count结果应该是0,无论协程怎么交替执行,最终咱们想象的结果就是0,但事实上并不是!!! // 那为什么结果不为0呢?可以参考上图的流程,最终值不为0的执行思路。 fmt.Printf("count = %d\n", count) } 

二.互斥锁Mutex

1 互斥锁概述

方法名 功能
func (m *Mutex) Lock() 获取互斥锁
func (m *Mutex) Unlock() 释放互斥锁
互斥锁是一种常用的控制共享资源访问的方法,它能够保证同一时间只有一个goroutine可以访问共享资源。 Go语言中使用sync包中提供的Mutex类型来实现互斥锁。 使用互斥锁能够保证同一时间有且只有一个goroutine进入临界区,其他的goroutine则在等待锁。 当互斥锁释放后,等待的goroutine才可以获取锁进入临界区,多个goroutine同时等待一个锁时,唤醒的策略是随机的。 

2使用互斥锁Mutex同步协程

package main import ( "fmt" "sync" ) var ( count int wg sync.WaitGroup /* 互斥锁: "sync.Mutex"为互斥锁,"Lock()"进行加锁,"Unlock()"进行解锁。 使用"Lock()"加锁后,便不能再次对其进行加锁,直到利用Unlock()解锁对其解锁后,才能再次加锁。 互斥锁适用于读写不确定场景,即读写次数没有明显的区别,性能相对来说比较低(因为同一个时刻仅有一个协程可以操作)。 */ lock sync.Mutex ) func add() { defer wg.Done() for i := 1; i <= 1000000; i++ { // 加锁,确保一个协程在执行逻辑的时候另外的协程不被执行。 lock.Lock() count++ // 解锁 lock.Unlock() } } func sub() { defer wg.Done() for i := 1; i <= 1000000; i++ { // 加锁 lock.Lock() count-- // 解锁 lock.Unlock() } } func main() { // 开启2个协程等待 wg.Add(2) go add() go sub() wg.Wait() // 使用互斥锁同步协程,从而解决多协程在对同一个资源处理时数据同步的问题。 fmt.Printf("count = %d\n", count) } 

三.读写互斥锁RWMutex

1 读写互斥锁概述

方法名 功能
func (rw *RWMutex) Lock() 获取写锁
func (rw *RWMutex) Unlock() 释放写锁
func (rw *RWMutex) RLock() 获取读锁
func (rw *RWMutex) RUnlock() 释放读锁
func (rw *RWMutex) RLocker() Locker 返回一个实现Locker接口的读写锁
互斥锁是完全互斥的,但是实际上有很多场景是读多写少的,当我们并发的去读取一个资源而不涉及资源修改的时候是没有必要加互斥锁的。 这种场景下使用读写锁是更好的一种选择。读写锁在Go语言中使用sync包中的RWMutex类型。 sync.RWMutex提供了如上表所示的5个方法。读写锁分为两种:读锁和写锁。 当一个goroutine获取到读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待。 而当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待。 

2 读写锁RWMutex引入

package main import ( "fmt" "sync" "time" ) var ( /* 读写锁: RWMutex是一个读写锁,其经常用于读次数远远多于写次数的场景。 读写锁的好处是多个协程同时在读的时候,数据之间不产生影响,锁不产生影响。 但多个协程出现了同时读和写的情况下才会产生影响,锁就会产生影响。 */ lock sync.RWMutex wg sync.WaitGroup ) func read(id int) { defer wg.Done() // 加读锁,如果只是读数据,那么这个锁不产生影响,但是读写同时发生的时候,就会有影响 lock.RLock() fmt.Printf("开始读取数据...ID = %d\n", id) time.Sleep(time.Second * 5) fmt.Printf("读取数据完成...ID = %d\n", id) // 解读锁 lock.RUnlock() } func write() { defer wg.Done() lock.Lock() fmt.Printf("开始修改数据...\n") time.Sleep(time.Second * 3) fmt.Printf("修改数据完成...\n") lock.Unlock() } func main() { wg.Add(11) // 启动协程: 模拟"读多写少"场景,10个线程读(无视锁),总耗时仅需5秒。 for i := 1; i <= 10; i++ { go read(i) } go write() wg.Wait() fmt.Println("main程序运行完成,程序已退出!") } 
目录
相关文章
|
Kotlin
Kotlin协程的取消机制:深入理解和优雅实现
本文详细探讨了Kotlin协程的取消机制,介绍了除直接使用`Job`的`cancel`方法外的多种优雅实现策略,如`CompletableDeferred`、`isActive`检查、`ensureActive`、`yield`及`CoroutineScope`的取消等。通过这些方法,可以更好地管理协程生命周期,确保资源正确释放,提升代码健壮性和可维护性。
246 12
|
Go 调度
Golang语言goroutine协程篇
这篇文章是关于Go语言goroutine协程的详细教程,涵盖了并发编程的常见术语、goroutine的创建和调度、使用sync.WaitGroup控制协程退出以及如何通过GOMAXPROCS设置程序并发时占用的CPU逻辑核心数。
612 4
Golang语言goroutine协程篇
|
运维 API 计算机视觉
深度解密协程锁、信号量以及线程锁的实现原理
深度解密协程锁、信号量以及线程锁的实现原理
207 2
|
SQL 安全 Java
golang为什么不支持可重入锁?
本文对比分析了Java与Go语言中锁机制的不同。在Java中,无论是`synchronized`关键字还是`ReentrantLock`都支持可重入特性,通过维护一个计数器来跟踪锁的嵌套级别,确保同一线程可以多次获取同一把锁而不会造成死锁。然而,Go语言的`sync.Mutex`并不支持这一特性,其设计理念认为可重入锁往往指向代码设计问题,鼓励开发者重构代码以避免此类需求。文章进一步解释了这种设计理念背后的原因,并提供了替代方案示例。总体而言,Go语言试图从设计层面避免潜在的代码问题,尽管这可能会增加一定的开发复杂性。
323 3
golang为什么不支持可重入锁?
|
存储 算法 Java
关于python3的一些理解(装饰器、垃圾回收、进程线程协程、全局解释器锁等)
该文章深入探讨了Python3中的多个重要概念,包括装饰器的工作原理、垃圾回收机制、进程与线程的区别及全局解释器锁(GIL)的影响等,并提供了详细的解释与示例代码。
209 0
|
NoSQL Unix 编译器
Golang协程goroutine的调度与状态变迁分析
文章深入分析了Golang中goroutine的调度和状态变迁,包括Grunnable、Gwaiting、Grunning和Gsyscall等状态,以及它们之间的转换条件和原理,帮助理解Go调度器的内部机制。
233 0
|
Python
python 协程 自定义互斥锁
【8月更文挑战第6天】这段代码展示了如何在Python的异步编程中自定义一个互斥锁(`CustomMutex`类)。该类通过`asyncio.Lock`实现,并提供`acquire`和`release`方法来控制锁的获取与释放。示例还包含了使用此自定义锁的场景:两个任务(`task1`和`task2`)尝试按序获取锁执行操作,直观地演示了互斥锁的作用。这有助于理解Python协程中互斥锁的自定义实现及其基本用法。
162 0
|
消息中间件 算法 Java
(十四)深入并发之线程、进程、纤程、协程、管程与死锁、活锁、锁饥饿详解
本文深入探讨了并发编程的关键概念和技术挑战。首先介绍了进程、线程、纤程、协程、管程等概念,强调了这些概念是如何随多核时代的到来而演变的,以满足高性能计算的需求。随后,文章详细解释了死锁、活锁与锁饥饿等问题,通过生动的例子帮助理解这些现象,并提供了预防和解决这些问题的方法。最后,通过一个具体的死锁示例代码展示了如何在实践中遇到并发问题,并提供了几种常用的工具和技术来诊断和解决这些问题。本文旨在为并发编程的实践者提供一个全面的理解框架,帮助他们在开发过程中更好地处理并发问题。
338 0
|
3月前
|
Go 调度 Python
Golang协程和Python协程用法上的那些“不一样”
本文对比了 Python 和 Go 语言中协程的区别,重点分析了调度机制和执行方式的不同。Go 的协程(goroutine)由运行时自动调度,启动后立即执行;而 Python 协程需通过 await 显式调度,依赖事件循环。文中通过代码示例展示了两种协程的实际运行效果。
177 7
|
2月前
|
数据采集 网络协议 API
协程+连接池:高并发Python爬虫的底层优化逻辑
协程+连接池:高并发Python爬虫的底层优化逻辑

推荐镜像

查看更多
下一篇