临界区 (Critical section)

在并发编程中,对共享资源的并发访问会导致意外或错误的行为,因此需要对访问共享资源的程序部分进行保护,以避免并发访问。这个被保护的部分就是关键部分或关键区域。它不能被一个以上的进程同时执行。通常情况下,关键部分访问共享资源,如数据结构、外围设备或网络连接,在多个并发访问的情况下,这些资源将无法正常运行。

RES_C29F8E7F552D.png

如下图,在互斥(mutex)的情况下,一个线程在需要访问共享资源时,通过使用锁的技术封锁一个关键部分,而其他线程必须等待轮到自己进入该部分。这可以防止两个或更多的线程共享同一内存空间并想访问一个共同的资源时发生冲突。

381px-Locks_and_critical_sections.jpg


互斥锁

互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个goroutine可以访问共享资源。 Go语言中使用sync包的Mutex类型来实现互斥锁。

使用互斥锁能够保证同一时间有且只有一个goroutine进入临界区,其他的goroutine则在等待锁;当互斥锁释放后,等待的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
29
package main

import (
	"fmt"
	"sync"
)


var num int64
var wg sync.WaitGroup
var mux sync.Mutex

func main()  {
	wg.Add(2)
	go add()
	go add()
	wg.Wait()
	fmt.Println(num) // output: 10000
}

func add()  {
	for idx := 0; idx < 5000; idx++ {
		mux.Lock()
		num++ // 非原子操作,因为有三个阶段在汇编中,分别内存到寄存器,寄存器自增,写回内存。
		mux.Unlock()
	}

	wg.Done()
}

读写互斥锁

互斥锁是完全互斥的,但是有很多实际的场景下是读多写少的,当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的,这种场景下使用读写锁是更好的一种选择。读写锁在Go语言中使用sync包中的RWMutex类型。

读写锁分为两种:读锁和写锁。 当一个goroutine获取读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package main

import (
	"fmt"
	"sync"
	"time"
)

var (
	num int64
	wg  sync.WaitGroup
	//mux   sync.Mutex
	//rwMux sync.RWMutex
)

type MuxReadWriter interface {
	Read()
	Write()
}

// MuxLockAdapter 互斥锁测试用例
type MuxLockAdapter struct {
	sync.Mutex
}

func (mux *MuxLockAdapter) Read() {
	mux.Lock()
	defer mux.Unlock()
	defer wg.Done()

	time.Sleep(10 * time.Millisecond) // 假设操作耗时1毫秒
}

func (mux *MuxLockAdapter) Write() {
	mux.Lock()
	defer mux.Unlock()
	defer wg.Done()

	num++
	time.Sleep(10 * time.Millisecond) // 假设操作耗时10毫秒
}

// RWMuxLockAdapter 读写互斥锁测试用例
type RWMuxLockAdapter struct {
	sync.RWMutex
}

func (mux *RWMuxLockAdapter) Read() {
	mux.RLock()
	defer mux.RUnlock()
	defer wg.Done()

	time.Sleep(10 * time.Millisecond) // 假设操作耗时1毫秒
}

func (mux *RWMuxLockAdapter) Write() {
	mux.Lock()
	defer mux.Unlock()
	defer wg.Done()

	num++
	time.Sleep(10 * time.Millisecond) // 假设操作耗时10毫秒
}

func Test(adapter MuxReadWriter) time.Duration {
	start := time.Now()

	for idx := 0; idx < 10; idx++ {
		wg.Add(1)
		go adapter.Write()
	}

	for idx := 0; idx < 1000; idx++ {
		wg.Add(1)
		go adapter.Read()
	}

	wg.Wait()
	return time.Now().Sub(start)
}

func main() {
	mux := &MuxLockAdapter{} // output: 11118ms [依赖执行主机,此值仅作参考]
	fmt.Printf("互斥锁耗时 %dms\n", Test(mux).Milliseconds())

	rwmux := &RWMuxLockAdapter{} // output: 123ms [依赖执行主机,此值仅作参考]
	fmt.Printf("读写互斥锁耗时 %dms", Test(rwmux).Milliseconds())
}