Go并发编程-基本并发原语-1.Mutex解决资源并发访问问题

多个goroutine同时更新用户信息,同时修改一个数据

互斥锁实现机制

互斥锁是并发控制的一个基本手段,是为了避免竞争而建立的一种并发控制机制

临界区

在并发编程中,如果程序的一部分会被并发访问或修改,那么,为了避免并发访问导致的意想不到的结果,这部分程序需要被保护起来,这部分被保护起来的程序,就叫做临界区

image-20240927104257410

同步原语

同步原语有哪些

互斥锁Mutex,读写锁RWMutex,并发编排WaitGroup,条件变量Cond,Channel等

同步原语的适用场景

  • 共享资源:并发地读写共享资源,会出现数据竞争的问题,所以需要Mutex,RWMutex这样的并发原语来保护
  • 任务编排:需要goroutine按照一定的规律执行,而goroutine之间有相互等待或者依赖的顺序关系,我们查那个用WaitGroup或者Channel实现
  • 消息传递: 信息交流以及不同的goroutine之间的线程安全的数据交流,常常使用Channel实现

Mutex的基本使用方法

image-20240927105302677

image-20240927105343919

当一个goroutine通过调用Lock方法获取了这个锁的拥有权的时候,其它请求锁的goroutine就会阻塞在Lock方法的调用上,直到锁被释放并且自己获取到了这个锁的拥有权

举个例子

image-20240927105758277

package main

import (
	"fmt"
	"sync"
)

func main() {
	var count int = 0
	var wg sync.WaitGroup
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go func() {
			defer wg.Done()
			for j := 0; j < 100000; j++ {
				count++
			}
		}()

	}
	wg.Wait()
	fmt.Println(count)
}

每次计算的值都不一样,而且结果也不是我们想要的100000

这就是因被并发访问修改导致的

我们给它加上锁

image-20240927110038842

package main

import (
	"fmt"
	"sync"
)

func main() {
	var count int = 0
	var wg sync.WaitGroup
	var mutex sync.Mutex
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go func() {
			defer wg.Done()
			mutex.Lock()
			for j := 0; j < 100000; j++ {
				count++
			}
			mutex.Unlock()
		}()

	}
	wg.Wait()
	fmt.Println(count)
}

使用场景

很多情况下,Mutex会嵌入到其它struct中使用,通过嵌入字段的方式,可以在struct上直接调用Lock/UnLock方法

如果嵌入的struct有多个字段,我们一般会把Mutex放在要控制的字段上面,然后用空格把字段分隔开

代码如下:

package main

import (
	"fmt"
	"sync"
)

type Counter struct {
	sync.Mutex
	Count int
}

func (c *Counter) Increment() {
	c.Count++
}

var wg sync.WaitGroup

func main() {
	counter := Counter{
		Count: 0,
	}
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go func() {
			defer wg.Done()
			counter.Lock()
			for j := 0; j < 100000; j++ {
				counter.Increment()
			}
			counter.Unlock()
		}()
	}
	wg.Wait()
	fmt.Println(counter.Count)
}

image-20240927111556965

race detector 竞态检测

编译器通过探测所有的内存访问,加入代码能监视对这些内存地址的访问,在代码运行的时候,race detector 就能监视到对共享变量的非同步访问,出现race的时候,就会打印出警告信息

在编译,测试或者运行代码的时候,加上race参数,就有可能发现并发问题

go run -race xxx.go

image-20240927110646558

image-20240927110703777