6.4.2 互斥锁

另一种同步访问共享资源的方式是使用互斥锁( mutex )。互斥锁这个名字来自互斥(mutual exclusion)的概念。互斥锁用于在代码上创建一个临界区,保证同一时间只有一个goroutine可以执行这个临界区代码。我们还可以用互斥锁来修正代码清单6-9中创建的竞争状态,如代码清单6-16所示。

代码清单6-16 listing16.go

01 // 这个示例程序展示如何使用互斥锁来
02 // 定义一段需要同步访问的代码临界区
03 // 资源的同步访问
04 package main
05
06 import (
07   "fmt"
08   "runtime"
09   "sync"
10 )
11
12 var (
13   // counter是所有goroutine都要增加其值的变量
14   counter int
15
16   // wg用来等待程序结束
17   wg sync.WaitGroup
18
19   // mutex 用来定义一段代码临界区
20   mutex sync.Mutex
21 )
22
23 // main是所有Go程序的入口
24 func main() {
25   // 计数加2,表示要等待两个goroutine
26   wg.Add(2)
27
28   // 创建两个goroutine
29   go incCounter(1)
30   go incCounter(2)
31
32   // 等待goroutine结束
33   wg.Wait()
34   fmt.Printf("Final Counter: %d\\n", counter)
35 }
36
37 // incCounter使用互斥锁来同步并保证安全访问,
38 // 增加包里counter变量的值
39 func incCounter(id int) {
40   // 在函数退出时调用Done来通知main函数工作已经完成
41   defer wg.Done()
42
43   for count := 0; count < 2; count++ {
44     // 同一时刻只允许一个goroutine进入
45     // 这个临界区
46     mutex.Lock()
47     {
48       // 捕获counter的值
49       value := counter
50
51       // 当前goroutine从线程退出,并放回到队列
52       runtime.Gosched()
53
54       // 增加本地value变量的值
55       value++
56
57       // 将该值保存回counter
58       counter = value
59     }
60     mutex.Unlock()
61     // 释放锁,允许其他正在等待的goroutine
62     // 进入临界区
63   }
64 }

counter 变量的操作在第46行和第60行的 Lock()Unlock() 函数调用定义的临界区里被保护起来。使用大括号只是为了让临界区看起来更清晰,并不是必需的。同一时刻只有一个goroutine可以进入临界区。之后,直到调用 Unlock() 函数之后,其他goroutine才能进入临界区。当第52行强制将当前goroutine退出当前线程后,调度器会再次分配这个goroutine继续运行。当程序结束时,我们得到正确的值 4 ,竞争状态不再存在。

results matching ""

    No results matching ""