第九章 基于共享变量的并发(一)竞争条件

2018-01-16  本文已影响0人  HaoR_W

一、Race Condition

基本概念

并发:我们不能确定事件x和y的执行顺序,则x和y是并发的

并发(concurrent) 并行(parallel)
宏观上同时,微观上交替 微观上同时进行

线程安全(thread safety):如果在并发(多线程)的情况下,这个函数依然可以正确地工作的话,那么我们就说这个函数是线程安全的,线程安全的函数不需要额外的同步工作。

竞争条件(race condition):描述一个系统或者进程的输出依赖于不受控制的事件(如并发的x和y)出现顺序或者出现时机。

数据竞争:两个不同线程中的指令访问同一块内存位置,且至少其中一条是写指令,同时不存在同步措施来保证两条指令的执行顺序。

A data race occurs when 2 instructions from different threads access the same memory location, at least one of these accesses is a write and there is no synchronization that is mandating any particular order among these accesses.

来源: stackoverflow

拓展阅读:CAS和SAS问题(原子操作相关)

比较并交换(compare and swap) wiki
ABA问题 wiki

二、避免数据竞争

1. 使用不可变(immutable)变量

即在程序初始化时便将变量的值确定下来且不再修改
优点:初始化完成后不需要同步就能实现并发安全
缺点:不可修改

2. 使用绑定(confinement)避免从多个goroutine访问变量

(1) 使用监控goroutine(monitor goroutine)

同一个goroutine内部的指令的顺序是可知的,故使用单独的goroutine来访问某一变量,其他需要访问该变量的goroutine通过channel向该goroutine发送请求来查询或更新——“不要使用共享数据来通信;使用通信来共享数据”。执行访问请求的goroutine被称为这个变量的监控(monitor)goroutine

// Package bank provides a concurrency-safe bank with one account.
package bank

var deposits = make(chan int) // send amount to deposit
var balances = make(chan int) // receive balance

// Deposit() 和 Balance() 只使用channel来与teller() 通信,实际访问和修改操作是由teller() 来执行
func Deposit(amount int) { deposits <- amount }
func Balance() int       { return <-balances }

func teller() {
    var balance int // balance is confined to teller goroutine
    for {
        // select 语句会等待某个case的条件满足,之后便跳出,故放在循环中
        select {
        case amount := <-deposits:
            balance += amount
        case balances <- balance:
        }
    }
}

func init() {
    go teller() // start the monitor goroutine
}
(2) 使用串行绑定(serial confinement)

若变量不能在其整个生命周期内被绑定到同一个goroutine,比如该变量需要在一条pipeline上的goroutines中传递,则对pipeline中的每一个goroutine,需通过channel传递该变量的地址给下一个阶段的goroutine,并保证在传递后不再直接访问该变量,则同一时刻只有一个goroutine可以访问这个变量。

type Cake struct{ state string }

func baker(cooked chan<- *Cake) {
    for {
        cake := new(Cake)
        cake.state = "cooked"
        cooked <- cake // baker never touches this cake again
    }
}

func icer(iced chan<- *Cake, cooked <-chan *Cake) {
    for cake := range cooked {
        cake.state = "iced"
        iced <- cake // icer never touches this cake again
    }
}

3. 保证goroutines访问变量时的互斥(mutual exclusion)

允许多个goroutine访问变量,但是确保在同一个时刻最多只有一个goroutine在访问。一般使用锁来实现,在《第九章 基于共享变量的并发(三)锁》中详细讨论。





1/15/2018

上一篇 下一篇

猜你喜欢

热点阅读