笔者在前文《Golang 入门 : 理解并发与并行》和《Golang 入门 : goroutine(协程)》中介绍了 Golang 对并发的原生支持以及 goroutine 的用法。本文我们来聊聊并发与并行带来的一些副作用。

并行编程之所以难道较高,根本的原因是需要处理共享资源的同步访问。比如在 Golang 中如果两个或者多个 goroutine 在没有互相同步的情况下,访问某个共享的资源,并试图同时读和写这个资源,就处于相互竞争的状态,这种情况被称作竞争条件(race candition)。竞争条件的存在是让并发程序变得复杂的地方,十分容易引起潜在问题。对一个共享资源的读和写操作必须是原子化的,换句话说,同一时刻只能有一个 goroutine 对共享资源进行读和写操作。

goroutine 引入的竞争条件

让我们来通过下面的 demo 来观察 goroutine 引入的竞争条件,为了让观察结果明显,我们采取了一些极端措施:

package main

import (
"sync"
"fmt"
"runtime"
) var(
// counter是所有goroutine都要增加其值的变量
counter int // wg用来等待程序结束
wg sync.WaitGroup
) // main是所有Go程序的入口
func main(){
runtime.GOMAXPROCS()
// 计数加2,表示要等待两个goroutine
wg.Add() // 创建两个goroutine
go incCounter()
go incCounter() // 等待goroutine结束
wg.Wait()
fmt.Println("Final Counter:", counter)
} // incCounter增加包里counter变量的值
func incCounter(id int){
// 在函数退出时调用Done来通知main函数工作已经完成
defer wg.Done() for count := ; count < ; count++{
// 捕获counter的值
value := counter // 当前goroutine从线程退出,并放回到队列
runtime.Gosched() // 增加本地value变量的值
value++ // 将该值保存回counter
counter = value
}
}

运行上面的代码,输出结果如下:

Final Counter: 

上面的程序中会对变量 counter 会进行 4 次读和写操作,每个 goroutine 执行两次。但是,程序终止时,counter 变量的值为 2。我们可以通过下面的图解来理解该程序的执行过程(此图来自互联网):

每个 goroutine 都会覆盖另一个 goroutine 的工作。这种覆盖发生在 goroutine 切换的时候。每个 goroutine 创造了一个 counter 变量的副本,之后就切换到另一个 goroutine。当 这个 goroutine 再次运行的时候,counter 变量的值已经改变了,但是 goroutine 并没有更新自己的那个副本的值,而是继续使用这个副本的值,用这个值递增,并存回 counter 变量,结果覆盖了另一个 goroutine 完成的工作。 下面是对程序执行过程的解释:

// 创建两个 goroutine
go incCounter()
go incCounter()

程序中通 go 关键字和 incCounter 函数创建了两个 goroutine。在 incCounter 函数内部对变量 counter 进行了读和写操作,而 counter 变量是这个示例程序里的共享资源。每个 goroutine 都会先读出这个 counter 变量的值,并把 counter 变量的副本存入一个叫作 value 的本地变量。之后 incCounter 函数对 value 变量加 1,并最终将这个新值存回到 counter 变量。incCounter 函数在对本地变量 value加 1 前调用了 runtime 包的 Gosched 函数,这个调用会将 goroutine 从当前线程退出,给其他 goroutine 运行的机会。在两次操作中间这样做的目的是强制调度器切换两个 goroutine,以便让竞争条件的效果变得更明显。

如果不是我们通过调用 Gosched 函数让竞争条件的效果变得明显,那么多次运行这段程序输出的 counter 值很可能是不一样的,会是 2,3,4 中的一个值。这种情况下导致的问题往往非常难以定位。

和其它编程语言一样,Golang 提供了原子函数和锁等机制来解决同步问题。但是使用这些机制并不会使并发编程变得更简单。接下来笔者将介绍 Golang 中提供的 channel(通道)功能,看它是如何以简洁的方式解决同步问题的。

参考:
《Go语言实战》

Golang 入门 : 竞争条件的更多相关文章

  1. Golang 入门 : channel(通道)

    笔者在<Golang 入门 : 竞争条件>一文中介绍了 Golang 并发编程中需要面对的竞争条件.本文我们就介绍如何使用 Golang 提供的 channel(通道) 消除竞争条件. C ...

  2. Java程序员的Golang入门指南(下)

    Java程序员的Golang入门指南(下) 4.高级特性 上面介绍的只是Golang的基本语法和特性,尽管像控制语句的条件不用圆括号.函数多返回值.switch-case默认break.函数闭包.集合 ...

  3. Java程序员的Golang入门指南(上)

    Java程序员的Golang入门指南 1.序言 Golang作为一门出身名门望族的编程语言新星,像豆瓣的Redis平台Codis.类Evernote的云笔记leanote等. 1.1 为什么要学习 如 ...

  4. Golang入门(3):一天学完GO的进阶语法

    摘要 在上一篇文章中,我们聊了聊Golang中的一些基础的语法,如变量的定义.条件语句.循环语句等等.他们和其他语言很相似,我们只需要看一看它们之间的区别,就差不多可以掌握了,所以作者称它们为&quo ...

  5. Golang入门(2):一天学完GO的基本语法

    摘要 在配置好环境之后,要研究的就是这个语言的语法了.在这篇文章中,作者希望可以简单的介绍一下Golang的各种语法,并与C和Java作一些简单的对比以加深记忆.因为这篇文章只是入门Golang的第二 ...

  6. 从头认识java-17.4 具体解释同步(2)-具体解释竞争条件

    这一章节我们来具体讨论一下竞争条件. 1.为什么会引起竞争条件? 因为操作缺失原子性. 2.什么是原子性? 所谓原子操作是指不会被线程调度机制打断的操作:这样的操作一旦開始,就一直运行到结束.中间不会 ...

  7. 理解竞争条件( Race condition)漏洞

    这几天一个叫做"Dirty COW"的linux内核竞争条件漏洞蛮火的,相关公司不但给这个漏洞起了个洋气的名字,还给它设计了logo(见下图),首页,Twitter账号以及网店.恰 ...

  8. Java多线程中的竞争条件、锁以及同步的概念

    竞争条件 1.竞争条件: 在java多线程中,当两个或以上的线程对同一个数据进行操作的时候,可能会产生“竞争条件”的现象.这种现象产生的根本原因是因为多个线程在对同一个数据进行操作,此时对该数据的操作 ...

  9. linux c编程:进程控制(二)_竞争条件

    前面介绍了父子进程,如果当多个进程企图对共享数据进行处理.而最后的结果又取决于进程运行的顺序时,就认为发生了竞争关系.通过下面的例子来看下 在这里标准输出被设置为不带缓冲的,于是父子进程每输出一个字符 ...

随机推荐

  1. [luoguP1272] 重建道路

    传送门 奇奇怪怪的分组背包. #include <cstdio> #include <cstring> #include <iostream> #define N ...

  2. 【HDOJ4322】Candy(费用流)

    题意:给N个孩子分配M个糖果. 有一个N*M的矩阵表示孩子和糖果的关系,若第i行第j列的数是1则表示第i个孩子喜欢第j个糖果,反之不喜欢. 已知,若一个孩子被分配到他喜欢的糖果那么他将获得K的快乐值, ...

  3. hdu - 1689 Just a Hook (线段树区间更新)

    http://acm.hdu.edu.cn/showproblem.php?pid=1698 n个数初始每个数的价值为1,接下来有m个更新,每次x,y,z 把x,y区间的数的价值更新为z(1<= ...

  4. Extjs6(六)——增删查改之查询

    本文主要实现的效果是:点击查询按钮,根据form中的条件,在Grid中显示对应的数据(如果form为空,显示全部数据) 一.静态页面 1.查询按钮 { text:'查询', handler: 'onS ...

  5. [bzoj3910]火车_并查集_倍增LCA

    火车 bzoj-3910 题目大意:给定一棵n个节点的树,你需要顺次经过m个互不相同的节点,如果一个节点在之前的路径上被经过过,它不必再被特意经过.问走过的路径长度. 注释:$1\le n\le 5\ ...

  6. 苹果装WIN 7

    一.准备:1.8G或以上的正版U盘或者移动硬盘,提前备份U盘数据(2013款air要求安装64位系统,市面上比较多U盘不是正版盘,一般做不成功,要换盘) 2.64位纯净版Windows7 ISO的下载 ...

  7. Linux纯Shell实现DNSPod动态域名

    http://www.anrip.com/post/872 开发背景: 公司有台嵌入式拨号上网设备,内置busybox和完整wget命令(支持https协议),但没有curl.python.ruby. ...

  8. 排列组合(permutation)系列解题报告

    本文解说4道关于permutation的题目: 1. Permutation:输出permutation--基础递归 2. Permutation Sequence: 输出字典序排列的第k个permu ...

  9. node+express+mysql小例子

    连接:https://www.cnblogs.com/humaotegong/p/5671009.html https://www.cnblogs.com/mibear/p/nodejs.html?u ...

  10. JAVA编程思想(2) - 操作符(二)

    5. 直接常量 -一般来说,假设程序里使用了"直接常量",编译器能够准确的知道要生成什么样的类型.但有时候却是模棱两可的. 这时候须要我们对编译器进行适当的"指导&quo ...