Go学习笔记10-Mutex

临界区

当程序并发地运行时,多个goroutine不应该同时访问那些修改共享资源的代码。这些修改共享资源的代码称为临界区。

Mutex

Mutex用于提供一种加锁机制,可确保在某一时刻只有一个协程在临界区运行,以防止出现竞态条件。Mutex可以在sync包内找到。Mutex定义了两个方法:LockUnlock。所有在Lock和Unlock之间的代码,都只能由一个Go协程执行,避免了竞态条件。如果有一个Go协程已经持有了锁,当其它协程试图获得该锁时,这些协程会被阻塞,直到Mutex解除锁定为止。

mutex.Lock()
{
    // 放置会出现竞态条件代码
    x = x + 1
}
mutex.Unlock()

含有竞态条件的程序

package main

import (
    "fmt"
    "sync"
)

var x = 0

func increment(wg *sync.WaitGroup) {
    x = x + 1 // 当多个并发的协程试图访问x的值,就会发生竞态条件
    wg.Done()
}

func main() {
    w := sync.WaitGroup{}
    for i := 0; i < 1000; i++ {
        w.Add(1)
        go increment(&w)
    }
    w.Wait()
    fmt.Println("final value of x:", x)
}

使用Mutex

这里使用Mutex修复上述竞态条件问题:

package main

import (
    "fmt"
    "sync"
)

var x = 0

// 注意这里必须传递Mutex的地址
func increment(wg *sync.WaitGroup, m *sync.Mutex) {
    m.Lock()
    x = x + 1 // 任何时刻都只允许一个协程执行这段代码
    m.Unlock()
    wg.Done()
}

func main() {
    w := sync.WaitGroup{}
    m := sync.Mutex{}
    for i := 0; i < 1000; i++ {
        w.Add(1)
        go increment(&w, &m)
    }
    w.Wait()
    fmt.Println("final value of x:", x)
}

使用信道处理竞态条件

这里使用Channel修复上述竞态条件问题:

package main

import (
    "fmt"
    "sync"
)

var x = 0

func increment(wg *sync.WaitGroup, ch chan bool) {
    ch <- true
    // 由于缓冲信道的容量为1,所以任何其它协程试图写入该信道时,
    // 都会发生阻塞,直到x增加后,信道的值才会被读取,完成解锁。
    x = x + 1
    <-ch
    wg.Done()
}

func main() {
    w := sync.WaitGroup{}
    ch := make(chan bool, 1)
    for i := 0; i < 1000; i++ {
        w.Add(1)
        go increment(&w, ch)
    }
    w.Wait()
    fmt.Println("final value of x:", x)
}

Mutex还是Channel?

Mutex和Channel都可以用来解决竞态条件问题。那么如何选择?

总体来说,当Go协程需要与其它协程通信时,可以使用信道。而当只允许一个协程访问临界区时,可以使用Mutex。就上述代码而言,使用Mutex更直观一些。

reference:

https://studygolang.com/articles/12598