竞争条件指的是程序在多个线程交叉执行时,引发的不可预知的错误。
1 | package main |
这段代码可能会出现如下三种不同情况:
- 不打印任何东西;第6行早于第9行执行
- 打印
the value is 0
;第9行和第10行早于第6行执行 - 打印
the value is 1
;执行第9行后,然后执行第6行,再执行第10行
竞态会导致程序的结果是不可预期的,而且不方便发现和调试。
怎么解决这个问题呢?
最简单的方法就是对访问到了变量的地方进行加锁操作。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package main
import "fmt"
import "sync"
var data int
var mu sync.Mutex
func main() {
go func() {
mu.Lock()
data++
mu.Unlock()
}()
mu.Lock()
if data==0{
fmt.Printf("the value is %v.\n", data)
}
mu.Unlock()
}
这样的话就不会产生竞态问题,但这样看上去不怎么优雅,而且锁用多了会很影响程序的性能。
也可以使用golang
的channel
,使data++
操作早于print
,所以下面的程序不会输出任何东西。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package main
import "fmt"
var data int
var d = make(chan int)
func main() {
go func() {
data++
d <- data
}()
data = <-d //如果没有数据将一直等待
if data == 0{
fmt.Printf("the value is %v.\n", data)
}
}
数据竞争发生的条件:
- 两个或两个以上的线程同时访问相同的变量
- 最少一个为写操作
- 没有使用任何同步机制
一.未使用任何同步机制的例子1
2
3
4
5
6
7
8
9
10
11
12// vim bank.go
package bank
var balance int
func Deposit(amount int) {
balance = balance + amount
}
func Balance() int {
return balance
}
1 | // vim bank_main.go |
执行go run src/bank_main.go
,意外的发现没有打印任何信息。这是为什么呢?因为运行Go
程序时,至少会启动一个main goroutine
,当main goroutine
早于其它协程执行之前退出,其它协程还没来得及执行就结束了。
执行go run -race src/bank_main.go
却打印了= 200
。
当两个goroutine
同时访问balance
变量时,假如Alice读取了balance
并要将数据写入到balance
的时候,如果此时切换到Bob,并将数据写入到了balance
,然后再切换到Alice,并写入balance
,这样就会将Bob的操作进行覆盖,这就是发生了数据竞争。
二.使用channel
避免多个goroutine
访问变量,解决数据竞争问题1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package bank
var deposits = make(chan int) // send amount to deposit
var balances = make(chan int) // receive balance
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 amount := <-deposits:
balance += amount
case balances <- balance:
}
}
}
func init() {
go teller() // start the monitor goroutine
}
执行go run -race src/bank_main.go
未发现竟争条件问题
三.允许多个goroutine
访问变量,但是在同一个时刻最多只有一个goroutine
访问。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29package main
import (
"fmt"
)
var (
sema = make(chan struct{}, 1)
balance int
)
func Deposit(amount int) {
sema <- struct{}{}
balance += amount
fmt.Print("balance", balance)
<-sema
}
func Balance() int {
sema <- struct{}{}
b := balance
<-sema
return b
}
func main() {
go Deposit(10)
}
1 | import "sync" |
使用Lock
与channel
有什么优缺点?
Deadlocks, Livelocks, Starvation
Deadlocks(死锁),在维基百科的解释为:In concurrent computing, a deadlock is a state in which each member of a group is waiting for another member, including itself, to take action, such as sending a message or more commonly releasing a lock.
图片来源:wikipedia
进程P1
持有资源R2
,进程P2
持有资源R1
,当进程P1
请求资源R1
,进程P2
请求资源R2
的时候这种情况下就会发生死锁,如果没有外部干预进程会一直卡死。
死锁发生必须的4个条件,因为这个4个条件来源的于G. Coffman, Jr.的一篇,因此也被称为Coffman conditions
。
- 互斥:一个资源在同一时间只能被一个进程访问
- 等待条件:进程同时占用至少一个资源,而且同时请求对其它的资源
- 非抢占:资源只能被占用它的那个进程释放
- 循环等待:比如
P1
等待P2
释放资源,P2
等待P1
释放,形成了环状结构
使用Golang
模拟死锁发生1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38package main
import (
"fmt"
"sync"
"time"
)
type value struct {
mu sync.Mutex
value int
}
var wg sync.WaitGroup
var printSum = func(v1,v2 *value) {
defer wg.Done()
v1.mu.Lock()
defer v1.mu.Unlock()
time.Sleep(2 * time.Second)
v2.mu.Lock()
defer v2.mu.Unlock()
fmt.Printf("sum=%v\n",v1.value+v2.value)
}
func main() {
var a, b value
wg.Add(2)
go printSum(&a, &b) //Goroutine p1
go printSum(&b, &a) // Goroutine p2
wg.Wait()
}
Golang
居然检测出来发生了死锁,那Golang
是如何进行死锁检测的呢?
图片来源:Concurrenecy in Go
程序运行时,goroutine p1
获取a
锁,此时p1
持有a
的锁,休眠,然后goroutine p2
开始执行,并持有资源b
的锁,然后p1
请求资源b
,p2
请求资源a
,发生死锁。
Livelocks(活锁)
活锁就是程序一直在运行,但是一直没有改变进程本身的状态。举个例子,比如你走在一个狭窄的通道,对面迎过来一个人,他看见了你就走到了另一边打算让你通过,而你也走到了另一边,当你们一直重复这两个动作时就发生了活锁。
Starvation
In computer science, resource starvation is a problem encountered in concurrent computing where a process is perpetually denied necessary resources to process its work.
在计算机科学中,资源匮乏是并发计算中遇到的一个问题,在该问题中,永久拒绝某个进程来处理其工作所需的资源
Ref:
1.https://en.wikipedia.org/wiki/Race_condition
2.The Go programing language
3.The Go Memory Model
4.Concurrency in Go
5.https://en.wikipedia.org/wiki/Deadlock
6.(https://github.com/kat-co/concurrency-in-go-src(https://github.com/kat-co/concurrency-in-go-src)
7.https://en.wikipedia.org/wiki/Starvation_(computer_science))