GoLang之使用sync.Mutex互斥锁、sync.RWMutex读写互斥锁

注:本文基于Windos系统上Go SDK v.1.18进行操作

1.go的并发安全问题演示

以下代码每次都会输出不同的值,比如6629、10000、6948等等,正常结果应该是10000

image-20220224144229470

image-20220224144849085

2.sync.Mutex互斥锁

image-20220224144345012

image-20220224145154211

3.sync.RWMutex读写互斥锁

image-20220224151054484

image-20220224151855565

image-20220224152003045

4.互斥锁与读写互斥锁性能比较

下面我们使用代码构造一个读多写少的场景,然后分别使用互斥锁和读写锁查看它们的性能差异。

var (
	x       int64
	wg      sync.WaitGroup
	mutex   sync.Mutex
	rwMutex sync.RWMutex
)

// writeWithLock 使用互斥锁的写操作
func writeWithLock() {
	mutex.Lock() // 加互斥锁
	x = x + 1
	time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒
	mutex.Unlock()                    // 解互斥锁
	wg.Done()
}

// readWithLock 使用互斥锁的读操作
func readWithLock() {
	mutex.Lock()                 // 加互斥锁
	time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒
	mutex.Unlock()               // 释放互斥锁
	wg.Done()
}

// writeWithLock 使用读写互斥锁的写操作
func writeWithRWLock() {
	rwMutex.Lock() // 加写锁
	x = x + 1
	time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒
	rwMutex.Unlock()                  // 释放写锁
	wg.Done()
}

// readWithRWLock 使用读写互斥锁的读操作
func readWithRWLock() {
	rwMutex.RLock()              // 加读锁
	time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒
	rwMutex.RUnlock()            // 释放读锁
	wg.Done()
}

func do(wf, rf func(), wc, rc int) {
	start := time.Now()
	// wc个并发写操作
	for i := 0; i < wc; i++ {
		wg.Add(1)
		go wf()
	}

	//  rc个并发读操作
	for i := 0; i < rc; i++ {
		wg.Add(1)
		go rf()
	}

	wg.Wait()
	cost := time.Since(start)
	fmt.Printf("x:%v cost:%v\n", x, cost)

}

我们假设每一次读操作都会耗时1ms,而每一次写操作会耗时10ms,我们分别测试使用互斥锁和读写互斥锁执行10次并发写和1000次并发读的耗时数据。
从最终的执行结果可以看出,使用读写互斥锁在读多写少的场景下能够极大地提高程序的性能。

// 使用互斥锁,10并发写,1000并发读
do(writeWithLock, readWithLock, 10, 1000) // x:10 cost:1.466500951s

// 使用读写互斥锁,10并发写,1000并发读
do(writeWithRWLock, readWithRWLock, 10, 1000) // x:10 cost:117.207592ms

5.附

当runtime.GOMAXPROCS(1)为1的时候,无论执行多少次,都输出10000

package main

import (
	"fmt"
	"runtime"
	"sync"
)

var wg sync.WaitGroup
var x int

func add() {
	for i := 0; i < 5000; i++ {
		x = x + 1
	}
	wg.Done()
}
func main() {
	runtime.GOMAXPROCS(1)

	wg.Add(2)
	go add()
	go add()
	wg.Wait()
	fmt.Println(x) //永远都输出10000
}

当runtime.GOMAXPROCS(2)为1的时候,输出各种多种多样的结果

package main

import (
	"fmt"
	"runtime"
	"sync"
)

var wg sync.WaitGroup
var x int

func add() {
	for i := 0; i < 5000; i++ {
		x = x + 1
	}
	wg.Done()
}
func main() {
	runtime.GOMAXPROCS(2)

	wg.Add(2)
	go add()
	go add()
	wg.Wait()
	fmt.Println(x)
	//7772
	//8314
	//5517

}