并发本身并不复杂,但是因为有了资源竞争的问题,就使得我们开发出好的并发程序变得复杂起来,因为会引起很多莫名其妙的问题。
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之并发资源竞争(互斥锁)的更多相关文章

  1. Golang之并发资源竞争(读写锁)

    前面的有篇文章在讲资源竞争的时候,提到了互斥锁.互斥锁的根本就是当一个goroutine访问的时候,其他goroutine都不能访问,这样肯定保证了资源的同步,避免了竞争,不过也降低了性能. 仔细剖析 ...

  2. python 并发编程 多进程 互斥锁 目录

    python 并发编程 多进程 互斥锁 模拟抢票 互斥锁与join区别

  3. c++并发编程之互斥锁(mutex)的使用方法

    1. 多个线程访问同一资源时,为了保证数据的一致性,最简单的方式就是使用 mutex(互斥锁). 引用 cppreference 的介绍: The mutex class is a synchroni ...

  4. python 并发编程 多进程 互斥锁

    运行多进程  每个子进程的内存空间是互相隔离的 进程之间数据不能共享的 一 互斥锁 但是进程之间都是运行在一个操作系统上,进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终 ...

  5. 悲观的并发策略——Synchronized互斥锁

    volatile既然不足以保证数据同步,那么就必须要引入锁来确保.互斥锁是最常见的同步手段,在并发过程中,当多条线程对同一个共享数据竞争时,它保证共享数据同一时刻只能被一条线程使用,其他线程只有等到锁 ...

  6. python 并发编程 多线程 互斥锁

    互斥锁 并行变成串行,牺牲效率 保证数据安全,实现局部串行 保护不同的数据,应该加不同的锁 现在一个进程 可以有多个线程 所有线程都共享进程的地址空间 实现数据共享 共享带来问题就会出现竞争 竞争就会 ...

  7. python 并发编程 多进程 互斥锁与join区别

    互斥锁与join 互斥锁和join都可以把并发变成串行 以下代码是用join实现串行 from multiprocessing import Process import time import js ...

  8. 并发编程 Process 互斥锁

    进程理论 程序与进程的区别 ''' 程序不是存在硬盘上的代码,相对来说是静态的 进程表示程序在执行的过程,是动态的 ''' 进程的调度 先来先服务调度算法 '''对长作业有利,对短作业无益''' 短作 ...

  9. C++ 并发编程之互斥锁和条件变量的性能比较

    介绍 本文以最简单生产者消费者模型,通过运行程序,观察该进程的cpu使用率,来对比使用互斥锁 和 互斥锁+条件变量的性能比较. 本例子的生产者消费者模型,1个生产者,5个消费者. 生产者线程往队列里放 ...

随机推荐

  1. ASP.NET MVC 通过ActionFilterAttribute来实现防止重复提交

    实现思想:每个页面打开的时候会在页面的隐藏控件自动生成一个值并将这个值赋值session,当提交方法的时候会在过滤器的时候进行获取session和页面传值过来的隐藏控件的值进行比较,如果值相同的话,重 ...

  2. Vue前端数据采集 埋点 追踪用户系列行为

    什么是埋点?  综合    vue埋点 埋点分析,是网站分析的一种常用的数据采集方法.数据埋点分为初级.中级.高级三种方式.数据埋点是一种良好的私有化部署数据采集方式. 埋点技术如何采集数据,有何优缺 ...

  3. mysql--对行(表中数据)的增删改查

    一.插入数据(增加)insert 1.插入数据(顺序插入) 语法一: INSERT INTO 表名(字段1,字段2,字段3…字段n) VALUES(值1,值2,值3…值n); #指定字段来插入数据,插 ...

  4. Java50道经典习题-程序18 乒乓球赛

    题目:两个乒乓球队进行比赛,各出三人.甲队为a,b,c三人,乙队为x,y,z三人.已抽签决定比赛名单.有人向队员打听比赛的名单. a说他不和x比,c说他不和x,z比,请编程序找出三队赛手的名单.分析: ...

  5. “全栈2019”Java异常第十三章:访问异常堆栈跟踪信息

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java异 ...

  6. Mybatis 类的转换器

    想一个事情 ,例: 我数据库的表 定义了一个表student 里面有一个字段 stu_sex  int类型  可是我对应的实体类是 String 类型或者其他类型 这个时候 实体类与数据库表肯定对应不 ...

  7. Comet OJ - Contest #0题解

    传送门 菜爆了--总共只有一道题会做的--而且也没有短裙好难过 为啥必须得有手机才能注册账号啊喂--歧视么-- \(A\) 解方程 推一下柿子大概就是 \[x-\sqrt{n}=y+z+2\sqrt{ ...

  8. Spring-解决请求中文乱码问题

    解决spring请求中文乱码问题 1.web.xml添加编码拦截器 <filter> <filter-name>CharacterEncoding</filter-nam ...

  9. MySQL 跟中文相关

    convert ()

  10. JAVA数组的定义方式

    JAVA中,数组有以下几种定义方式: 1.先定义数组中元素的个数,然后再进行元素赋值 int[] a=new int[2]; a[0]='a'; a[1]='b';a[2]='c'; 2.元素个数与元 ...