并发本身并不复杂,但是因为有了资源竞争的问题,就使得我们开发出好的并发程序变得复杂起来,因为会引起很多莫名其妙的问题。
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 导出 Excel 身份证格式显示格式问题

    <%#  Eval("数据").ToString()+" " %> 加上    Excel 中 不会显示科学计数法

  2. vue + echarts画圈圈

    <div class="chart-bar-left" id= "chartbar-left" style="margin-top:1%;&qu ...

  3. ASP.NET Core 2.0 in Docker on Windows Containers

    安装Docker for Windows https://store.docker.com/editions/community/docker-ce-desktop-windows 要想将一个ASP. ...

  4. hud 5124 lines(思维 + 离散化)

    http://acm.hdu.edu.cn/showproblem.php?pid=5124 lines   Problem Description: John has several lines. ...

  5. Android 获取模拟器与真机数据库

    模拟器: localuser:~ localhost$ adb shell shell@android:/ $ su // 数据库复制到 Download 下 shell@android:/ # cp ...

  6. python中的函数(基础)

    1.什么是函数 函数是指将一组数据的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用函数名即可 (函数就是对功能或者动作的封装) 2.函数的语法和定义 def 函数名() 函数体 调用: ...

  7. Java - io输入输出流 --转换流

    转换流  转换输出流 OutputStreamWriter: 说明: /* * OutputStreamWriter 这个类的作用 * 就是指定输出流的编码格式 * 这个类的构造方法 需要传递 一个输 ...

  8. 用flask实现一个用户登录的功能

    #!/usr/bin/python #coding=utf-8 from flask import Flask,session,redirect,url_for,request app=Flask(_ ...

  9. html中object和embed标签的区别

    ♦object定义一个嵌入的对象.请使用此元素向您的 XHTML 页面添加多媒体.此元素允许您规定插入 HTML 文档中的对象的数据和参数,以及可用来显示和操作数据的代码. ♦<object&g ...

  10. 总结day2 ---- while循环的简单使用, 格式化输出.运算符.以及编码的应用

    内容提要 一 : while 循环 while 的基本语句操作 如何终止循环 二 :格式化输出 三 :运算符号 四 :编码初识别 一 : while 循环 1  >>>>whi ...