C 语言 POSIX 线程目定义了互斥量,如果临界区的代码更新了全局变量的值,那么在临界区结束之后通过什么机制来保证全局变量的可视性?
在线程 unlock 的时候刷新自己缓存的值到主存,这样的话由于互斥访问所有的线程看到的都是最新的数据,并且临界区执行完成后的刷新保证后续的线程看到的也是最新的。
但是这样还有问题,就是线程是刷新线程本地的全部缓存到主存?还是只是和临界区相关变量的缓存到主存?具体的底层指令是啥或者原理是啥?
其实 Java 也有一样的问题,搜索给出的答案都是内存屏障、Happen-before 原则,但是没有看到内存屏障、Happen-before 这些东西的底层原理或者伪代码解释~
希望能给出详细的解释或者权威的引用文档~
1
LeegoYih 2023-04-13 17:25:51 +08:00
八股文要从硅原子原理开始背了吗?
|
2
yinmin 2023-04-13 17:32:33 +08:00
多线程访问同一个变量,这个变量是存放在同一个地址空间里的,没有同步概念,也没有可视概念。
锁是为了事务的原子性。例如:a=a+1 ,a 原来值是 1 ,如果 2 个线程同时操作,都读到 1 ,然后都是回写 2 ,就出问题了。1 个线程加锁后,另外一个线程就会等到解锁再操作,避免了冲突。 |
3
Suomea OP |
4
Ericcccccccc 2023-04-13 17:35:40 +08:00
原理是搜 MESI, 这个其实是硬件保证的. 不同 cpu 架构还有差别. (比如某些架构下, 天生强一致, 不需要内存屏障也能行
|
5
Suomea OP @yinmin 那如果多个线程是在多个 CPU 核心上运行呢,如果全局变量没有加 volatile 修饰,那么这个变量会缓存在 CPU 内部的 L1 吗?如果会临界区结束,要刷新 L1 到主存吗?如果要又是什么机制呢?啊啊啊~~~
|
6
Suomea OP @Ericcccccccc 假设 IA-32 。那么锁是怎么和 MESI 机制结合的呢?是不是进入临界区之后,所有的缓存都使用了 MESI 机制,而不是临界区的缓存就不使用 MESI 机制了吗?
|
7
Inn0Vat10n 2023-04-13 18:36:25 +08:00
多核之间 cache 里数据的一致性问题是硬件管的,它有自己的一致性协议,软件这层不用管
|
8
dode 2023-04-14 09:14:03 +08:00
最下面就是 CPU ,寄存器,指令集,提供一些原语保证了
|
10
dode 2023-04-14 15:24:44 +08:00
硬件对同步的支持-TAS 和 CAS 指令
https://www.cnblogs.com/upnote/p/13193856.html cpu 硬件同步原语 https://baike.baidu.com/item/CAS/7371138 《计算机组成原理》 相关书籍 |
11
Suomea OP @dode 这个我知道,我们可以通过 CAS + LOCK# 来实现互斥,即加锁。但是注意这里只是锁,而不是临界区的共享变量。举个自旋锁例子
mutex_t { int flag; // 初始化等于 0 。1 表示锁被占用 } lock(mutex_t *mutex) { while(asm LOCK# CAS(mutex->flag, 0, 1) = 0) ; } unlock(mutex_t *mutex) { mutex->flag = 0; } int a; void 临界区() { lock(); …… // 对 a 进行操作 unlock(); } 这里 CAS 只是保证了锁的正确性。但是我的问题是临界区的代码并没有对 a 进行额外的(刷新缓存,或者什么的)操作,至少代码上看是这样。那难道临界区的所有语句都加上 LOCK#,不应该,因为 LOCK# 支持的指令有限。 |
12
Suomea OP |
13
dode 2023-04-14 17:17:38 +08:00
你看 java 这个例子
https://www.jianshu.com/p/06717ac8312c 并发编程-( 4 )-JMM 基础(总线锁、缓存锁、MESI 缓存一致性协议、CPU 层面的内存屏障) 3.3.2 、JMM 层面的内存屏障 ```java class VolatileExample { int a = 0; volatile boolean flag = false; public void writer() { a = 1; //1 flag = true; //2 } public void reader() { if (flag) { //3 int i = a; //4 ... } } } ``` 假设线程 A 执行 writer()方法之后,线程 B 执行 reader()方法,那么线程 B 执行 4 的时候一定能看到线程 A 写入的值吗?注意, [a 不是 volatile 变量] 。 答案是肯定的。因为根据 happens-before 规则,我们可以得到如下关系: 根据程序顺序规则,1 happens-before 2 ; 3 happens-before 4 。 根据 volatile 规则,2 happens-before 3 。 根据传递性规则,1 happens-before 4 。 因此,综合运用程序顺序规则、volatile 规则及传递性规则,我们可以得到 1 happens-before 4 ,即线程 B 在执行 4 的时候一定能看到 A 写入的值。 |