-
Notifications
You must be signed in to change notification settings - Fork 289
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(concurrency): add sync.Mutex:data race
- Loading branch information
Showing
1 changed file
with
76 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
* @Author: shgopher [email protected] | ||
* @Date: 2023-05-14 23:08:19 | ||
* @LastEditors: shgopher [email protected] | ||
* @LastEditTime: 2023-11-29 22:14:21 | ||
* @LastEditTime: 2023-11-30 23:43:50 | ||
* @FilePath: /GOFamily/并发/同步原语/README.md | ||
* @Description: | ||
* | ||
|
@@ -79,6 +79,81 @@ type Locker interface{ | |
Unlock() | ||
} | ||
``` | ||
Mutex 就实现了这个 Locker 接口 | ||
|
||
Locker 定义了锁的基本方法,加锁 + 解锁 | ||
|
||
### 什么是 data race 的本质 | ||
我们常说 data race 的情况,是多线程同时对于某块内存进行数据的变更,那么问题来了,这个地方谈到的同时,是真的物理层面的同时还是近似同时? | ||
|
||
关于 data race 中的 “同时” 通常是**指逻辑上的同时或近似同时,而不是物理层面严格的同一时刻。** | ||
|
||
主要原因有以下几点: | ||
|
||
- 现代 CPU 中,同一时刻真正执行指令的只有一个核心。不同核心之间以及同一核心的不同执行周期之间,不存在物理层面严格的同步。 | ||
- 即使在单核 CPU 上,由于指令流水线、内存缓存、分支预测等机制,实际执行顺序也可能与代码顺序不一致,很难定义物理层面严格的同步。 | ||
- 不同线程之间进行切换的时间间隔非常小 (几十到几百纳秒),对程序逻辑而言可以视为同时进行。 | ||
- 要构成 data race,不同线程对同一地址的访问之间间隔不能太长,必须在一个指令/操作的启始和完成之间,所以也符合逻辑上的近似同时。 | ||
|
||
所以,data race 中的 “同时” 就是指逻辑上近似同时,或者无法确定准确执行顺序的情况,而不是物理层面严格同一时刻。这种近似同时从程序角度就是可能造成冲突,需要进行同步处理。 | ||
### 检测 data race 的工具 | ||
并发问题不是一定能肉眼看出来的,如果只是基础的,容易看出来的也就罢了,但是很多隐藏的 data race 问题必须使用专业的工具去鉴别,go 语言提供了 `-race` 功能,在编译,测试,run 的时候,会自动检测到 data race 问题,并且给出详细的错误信息。 | ||
|
||
```bash | ||
go run -race main.go | ||
``` | ||
|
||
我们看一个例子 | ||
```go | ||
func main() { | ||
value := 0 | ||
for i := 0; i < 10000; i++ { | ||
go func(){ | ||
value ++ | ||
}() | ||
} | ||
time.Sleep(1000) | ||
fmt.Println(value) | ||
} | ||
``` | ||
本能的你会以为能输出 10000,但是结果确实 9000 多,而且还不一定,这是为啥呢? | ||
|
||
因为你以为 ++ 操作是原子操作,其实并不是。 | ||
|
||
++ 操作分为三个步骤 | ||
- 获取 value 值 | ||
- 值+1 | ||
- 将新值写回 | ||
|
||
这其实是三个步骤,10000 个线程,假如同时有 10 个去读了这个 value,在他们看来都是初始值是 0,然后他们+1,然后写回去了结果 value 是 1,相当于 10 个 goroutine 都去写,本来应该是 10,但是赋值回去都变成了 1 | ||
|
||
这个时候,如果你使用 run -race 就能检测出来 | ||
|
||
```bash | ||
go1 go run -race main.go | ||
================== | ||
WARNING: DATA RACE | ||
Read at 0x00c00010a018 by goroutine 8: | ||
main.main.func1() | ||
/Users/shgopher/Desktop/1/go1/main.go:23 +0x2c | ||
|
||
Previous write at 0x00c00010a018 by goroutine 6: | ||
main.main.func1() | ||
/Users/shgopher/Desktop/1/go1/main.go:23 +0x3c | ||
|
||
Goroutine 8 (running) created at: | ||
main.main() | ||
/Users/shgopher/Desktop/1/go1/main.go:22 +0x48 | ||
|
||
Goroutine 6 (finished) created at: | ||
main.main() | ||
/Users/shgopher/Desktop/1/go1/main.go:22 +0x48 | ||
================== | ||
9957 | ||
Found 1 data race(s) | ||
exit status 66 | ||
``` | ||
多线程多某个区域的内存进行同时 (或者近似同时) 操作,这就是数据竞争 | ||
|
||
### sync.Mutex 互斥锁的实现原理 | ||
go 语言互斥锁的实现非常简单,只有这一个结构体就是核心: | ||
|