Golang之并发资源竞争(互斥锁)
package main import (
"fmt"
"runtime"
"sync"
) var (
count int32
wg sync.WaitGroup
) func main() {
wg.Add()
go incCount()
go incCount()
wg.Wait()
fmt.Println(count)
} func incCount() {
defer wg.Done()
for i := ; i < ; i++ {
value := count
runtime.Gosched()
value++
count = value
}
}
这是一个资源竞争的例子。我们可以多运行几次这个程序,会发现结果可能是 2 ,也可以是 3 ,也可能是 4 。因为共享资源count
变量没有任何同步保护,所以两个goroutine都会对其进行读写,会导致对已经计算好的结果覆盖,以至于产生错误结果。这里我们演示一种可能,两个goroutine我们暂时称之为g1和g2。
g1读取到count为 0 。
然后g1暂停了,切换到g2运行,g2读取到count也为 0 。
g2暂停,切换到g1,g1对count+1,count变为 1 。
g1暂停,切换到g2,g2刚刚已经获取到值 0 ,对其+1,最后赋值给count还是 1 。
有没有注意到,刚刚g1对count+1的结果被g2给覆盖了,两个goroutine都+1还是 1 。
不再继续演示下去了,到这里结果已经错了,两个goroutine相互覆盖结果。我们这里的runtime.Gosched()
是让当前goroutine暂停的意思。退回执行队列,让其他等待的goroutine运行,目的是让我们演示资源竞争的结果更明显。注意,这里还会牵涉到CPU问题,多核会并行,那么资源竞争的效果更明显。
所以我们对于同一个资源的读写必须是原子化的,也就是说,同一时间只能有一个goroutine对共享资源进行读写操作。
共享资源竞争的问题,非常复杂,并且难以察觉,好在Go提供了一个工具来帮助我们检查,这个就是go build -race
命令。我们在当前项目目录下执行这个命令,生成一个可以执行文件,然后再运行这个可执行文件,就可以看到打印出的检测信息。
go build -race
多加了一个-race
标志,这样生成的可执行程序就自带了检测资源竞争的功能。下面我们运行,也是在终端运行。
./hello
我这里示例生成的可执行文件名是hello
,所以是这么运行的。这时候,我们看终端输出的检测结果。
hello ./hello
==================
WARNING: DATA RACE
Read at 0x0000011a5118 by goroutine :
main.incCount()
/Users/xxx/code/go/src/flysnow.org/hello/main.go: +0x76 Previous write at 0x0000011a5118 by goroutine :
main.incCount()
/Users/xxx/code/go/src/flysnow.org/hello/main.go: +0x9a Goroutine (running) created at:
main.main()
/Users/xxx/code/go/src/flysnow.org/hello/main.go: +0x77 Goroutine (finished) created at:
main.main()
/Users/xxx/code/go/src/flysnow.org/hello/main.go: +0x5f
================== Found data race(s)
看,找到一个资源竞争,连在那一行代码出了问题,都标示出来了。goroutine 7在代码 25 行读取共享资源value := count
,而这时goroutine 6正在代码 28 行修改共享资源count = value
,而这两个goroutine都是从main函数启动的,在 16、17 行,通过go关键字。
既然我们已经知道共享资源竞争的问题,是因为同时有两个或者多个goroutine对其进行了读写,那么我们只要保证,同时只有一个goroutine读写不就可以了。现在我们就看下传统解决资源竞争的办法——对资源加锁。
Go语言提供了atomic包和sync包里的一些函数对共享资源同步加锁,我们在此只看下sync:
sync包里提供了一种互斥型的锁,可以让我们自己灵活地控制那些代码,同时只能有一个goroutine访问,被sync互斥锁控制的这段代码范围,被称之为临界区。临界区的代码,同一时间,只能又一个goroutine访问。刚刚那个例子,我们还可以这么改造。
package main import (
"fmt"
"runtime"
"sync"
) var (
count int32
wg sync.WaitGroup
mutex sync.Mutex
) func main() {
wg.Add()
go incCount()
go incCount()
wg.Wait()
fmt.Println(count)
} func incCount() {
defer wg.Done()
for i := ; i < ; i++ {
mutex.Lock()
value := count
runtime.Gosched()
value++
count = value
mutex.Unlock()
}
}
实例中,新声明了一个互斥锁mutex sync.Mutex
。这个互斥锁有两个方法,一个是mutex.Lock()
,一个是mutex.Unlock()
。这两个之间的区域就是临界区,临界区的代码是安全的。
示例中我们先调用mutex.Lock()
对有竞争资源的代码加锁,这样当一个goroutine进入这个区域的时候,其他goroutine就进不来了,只能等待,一直到调用mutex.Unlock()
释放这个锁为止。
这种方式比较灵活,可以让代码编写者任意定义需要保护的代码范围,也就是临界区。除了原子函数和互斥锁,Go还为我们提供了更容易在多个goroutine同步的功能,这就是通道chan。
Golang之并发资源竞争(互斥锁)的更多相关文章
- Golang之并发资源竞争(读写锁)
前面的有篇文章在讲资源竞争的时候,提到了互斥锁.互斥锁的根本就是当一个goroutine访问的时候,其他goroutine都不能访问,这样肯定保证了资源的同步,避免了竞争,不过也降低了性能. 仔细剖析 ...
- python 并发编程 多进程 互斥锁 目录
python 并发编程 多进程 互斥锁 模拟抢票 互斥锁与join区别
- c++并发编程之互斥锁(mutex)的使用方法
1. 多个线程访问同一资源时,为了保证数据的一致性,最简单的方式就是使用 mutex(互斥锁). 引用 cppreference 的介绍: The mutex class is a synchroni ...
- python 并发编程 多进程 互斥锁
运行多进程 每个子进程的内存空间是互相隔离的 进程之间数据不能共享的 一 互斥锁 但是进程之间都是运行在一个操作系统上,进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终 ...
- 悲观的并发策略——Synchronized互斥锁
volatile既然不足以保证数据同步,那么就必须要引入锁来确保.互斥锁是最常见的同步手段,在并发过程中,当多条线程对同一个共享数据竞争时,它保证共享数据同一时刻只能被一条线程使用,其他线程只有等到锁 ...
- python 并发编程 多线程 互斥锁
互斥锁 并行变成串行,牺牲效率 保证数据安全,实现局部串行 保护不同的数据,应该加不同的锁 现在一个进程 可以有多个线程 所有线程都共享进程的地址空间 实现数据共享 共享带来问题就会出现竞争 竞争就会 ...
- python 并发编程 多进程 互斥锁与join区别
互斥锁与join 互斥锁和join都可以把并发变成串行 以下代码是用join实现串行 from multiprocessing import Process import time import js ...
- 并发编程 Process 互斥锁
进程理论 程序与进程的区别 ''' 程序不是存在硬盘上的代码,相对来说是静态的 进程表示程序在执行的过程,是动态的 ''' 进程的调度 先来先服务调度算法 '''对长作业有利,对短作业无益''' 短作 ...
- C++ 并发编程之互斥锁和条件变量的性能比较
介绍 本文以最简单生产者消费者模型,通过运行程序,观察该进程的cpu使用率,来对比使用互斥锁 和 互斥锁+条件变量的性能比较. 本例子的生产者消费者模型,1个生产者,5个消费者. 生产者线程往队列里放 ...
随机推荐
- WPF Image显示图片,文件被占用异常
imageControl.Source = this.GetBitmapImage(imagePath);//imageControl为WPF Image控件 public BitmapImage G ...
- 关于StreamReader.ReadToEnd方法
以前写抓取网页的代码喜欢用ReadToEnd,因为简单省事,后来发现,在爬取网页的时候,如果网速很慢,ReadToEnd超时的几率很大.使用Read改写后,超时几率大大减小,完整代码如下: /// & ...
- windows 10 自适应布局
https://msdn.microsoft.com/library/windows/apps/dn894631.aspx Use visual state triggers to build UI ...
- WPF 使用OCX控件速度很慢
最近公司项目,需要在wpf上面嵌入ocx控件,但是程序运行起来后,进行操作后,界面一直很卡,找了各种原因,没有找到原因,后来直接运行exe文件,速度顿时快了很多.
- 数据库访问接口(ODBC、OLE DB、ADO)
最近在学C#的数据库编程,对于数据库接口技术这块的知识一直比较模糊,网上查了不少资料,看了几天还是朦朦胧胧的,只能做些笔记再研究了. 我们都知道,“数据库”是指一组相关信息的集合,最早的计算机应用之一 ...
- Syncthing源码解析 - 启动过程
我相信很多朋友会认为启动就是双击一下Syncthing程序图标,随后就启动完毕了!如果这样认为,对,也不对!对,是因为的确是这样操作,启动了Syncthing:不对是因为在调试Syncthing启动过 ...
- 内置函数——sorted
对List.Dict进行排序,Python提供了两个方法对给定的List L进行排序,方法1.用List的成员函数sort进行排序,在本地进行排序,不返回副本方法2.用built-in函数sorted ...
- spring-mybatis源码追踪
启用一个扫描类 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <propert ...
- pandas.concat连接dataframe
https://blog.csdn.net/stevenkwong/article/details/52528616
- 分享一些JAVA常用的学习网站
常用学习网站freecodecamp 一个非常好的网站,教学模式类似游戏中的闯关,通过每关之后会有成就感,在该网站还有设有聊天室,可以进行相关的技术交流,很棒的学习网站. https://www.fr ...