Skip to content

Latest commit

 

History

History
185 lines (135 loc) · 4.53 KB

go-并发.md

File metadata and controls

185 lines (135 loc) · 4.53 KB

Go 并发机制

Goroutines

主函数返回时,所有的goroutine都会被直接打断,程序退出。

f()    // call f(); wait for it to return
go f() // create a new goroutine that calls f(); don't wait

以下的代码不能保证 f()函数执行过或者执行完毕。

func main() {
    go f()
}

select

如果多个 case 同时就绪时,select 会随机地选择一个执行,这样来保证每一个 channel 都有平等的被 select 的机会。

select 会有一个 default 来设置当其它的操作都不能够马上被处理时程序需要执行哪些逻辑。

下面的 select 语句会在 abort channel 中有值时,从其中接收值;无值时什么都不做。这是一个非阻塞的接收操作;反复地做这样的操作叫做“轮询 channel”。

select {
case <-abort:
    fmt.Printf("Launch aborted!\n")
    return
default:
    // do nothing
}

同步

sync.Mutex 互斥锁

通过 channel 实现互斥锁

我们可以用一个容量只有 1 的 channel 来保证最多只有一个 goroutine 在同一时刻访问一个共享变量。

一个只能为1和0的信号量叫做二元信号量(binary semaphore)。

var (
    sema    = make(chan struct{}, 1) // a binary semaphore guarding balance
    balance int
)

func Deposit(amount int) {
    sema <- struct{}{} // acquire token
    balance = balance + amount
    <-sema // release token
}

func Balance() int {
    sema <- struct{}{} // acquire token
    b := balance
    <-sema // release token
    return b
}

sync.Mutex

在 Lock 和 Unlock 之间的代码段中的内容 goroutine 可以随便读取或者修改,这个代码段叫做临界区。

每一个函数在一开始就获取互斥锁并在最后释放锁,从而保证共享变量不会被并发访问。这种函数、互斥锁和变量的编排叫作"监控 monitor"。

import "sync"

var (
    mu      sync.Mutex // guards balance
    balance int
)

func Deposit(amount int) {
    mu.Lock()
    balance = balance + amount
    mu.Unlock()
}

func Balance() int {
    mu.Lock()
    b := balance
    mu.Unlock()
    return b
}

go 里没有重入锁(Re-entrant lock),没法对一个已经锁上的 mutex 来再次上锁--这会导致程序死锁,没法继续执行下去。

sync.RWMutex读写锁

允许多个只读操作并行执行,但写操作会完全互斥。这种锁叫作“多读单写”锁(multiple readers, single writer lock),Go 语言提供的这样的锁是 sync.RWMutex

var mu sync.RWMutex
var balance int
func Balance() int {
    mu.RLock() // readers lock
    defer mu.RUnlock()
    return balance
}
func Write() {
    mu.Lock() // writers lock
    defer mu.Unlock()
    balance = 1
}

RLock 只能在临界区共享变量没有任何写入操作时可用。

RWMutex 只有当获得锁的大部分 goroutine 都是读操作,而锁在竞争条件下,也就是说,goroutine 们必须等待才能获取到锁的时候,RWMutex 才是最能带来好处的。

RWMutex 需要更复杂的内部记录,所以会让它比一般的无竞争锁的 mutex 慢一些。

WaitGroup

该类型有三个指针方法:Add,Done,Wait。

sync.WaitGroup 是一个结构体类型,其中有一个字段用四个字节表示给定计数,用四个字节表示等待计数。通过 Add 方法增大或减少给定计数:

wg.Add(3)
wg.Add(-3)

如果让给定计数变为负数,会引发一个运行恐慌。

还可以通过调用 Done 方法使给定计数减一。

wg.Done() // = wg.Add(-1)

当 wg.Done 让给定计数变为负数时,也会引发一个运行时恐慌。

当调用 Wait 方法时,会去检查给定计数,如果计数等于 0,那么该方法会立即返回,否则会阻塞,同时等待计数会加一。直到给定计数变为 0,才会唤醒所有阻塞的 goroutine,同时清零等待计数。

等待 goroutine 任务的完成有两种方法:

1.使用 channel

sign := make(chan struct{}, 2)
go func(){
    // Do something 
    sign <- struct{}	
}()
go func(){ 
    // Do something
    sign <- struct{}
}()
// 阻塞,直到两个goroutine都结束执行
<-sign
<-sign

2.使用 sync.WaitGroup

var wg sync.WaitGroup
wg.Add(2)

go func(){
    // Do something
    wg.Done()
}()

go func(){
    // Do something
    wg.Done()
}()

// 阻塞,直到 wg 的给定计数为 0,也就是两次 wg.Done() 都执行完毕
wg.Wait()