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个消费者. 生产者线程往队列里放 ...
随机推荐
- asp.net 导出 Excel 身份证格式显示格式问题
<%# Eval("数据").ToString()+" " %> 加上 Excel 中 不会显示科学计数法
- vue + echarts画圈圈
<div class="chart-bar-left" id= "chartbar-left" style="margin-top:1%;&qu ...
- ASP.NET Core 2.0 in Docker on Windows Containers
安装Docker for Windows https://store.docker.com/editions/community/docker-ce-desktop-windows 要想将一个ASP. ...
- hud 5124 lines(思维 + 离散化)
http://acm.hdu.edu.cn/showproblem.php?pid=5124 lines Problem Description: John has several lines. ...
- Android 获取模拟器与真机数据库
模拟器: localuser:~ localhost$ adb shell shell@android:/ $ su // 数据库复制到 Download 下 shell@android:/ # cp ...
- python中的函数(基础)
1.什么是函数 函数是指将一组数据的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用函数名即可 (函数就是对功能或者动作的封装) 2.函数的语法和定义 def 函数名() 函数体 调用: ...
- Java - io输入输出流 --转换流
转换流 转换输出流 OutputStreamWriter: 说明: /* * OutputStreamWriter 这个类的作用 * 就是指定输出流的编码格式 * 这个类的构造方法 需要传递 一个输 ...
- 用flask实现一个用户登录的功能
#!/usr/bin/python #coding=utf-8 from flask import Flask,session,redirect,url_for,request app=Flask(_ ...
- html中object和embed标签的区别
♦object定义一个嵌入的对象.请使用此元素向您的 XHTML 页面添加多媒体.此元素允许您规定插入 HTML 文档中的对象的数据和参数,以及可用来显示和操作数据的代码. ♦<object&g ...
- 总结day2 ---- while循环的简单使用, 格式化输出.运算符.以及编码的应用
内容提要 一 : while 循环 while 的基本语句操作 如何终止循环 二 :格式化输出 三 :运算符号 四 :编码初识别 一 : while 循环 1 >>>>whi ...