问题 我应该互斥锁一个变量吗?


如果在多个线程之间共享一个32位变量,我应该在变量周围放置一个互斥锁吗?例如,假设1个线程写入32位计数器,第2个线程读取它。第二个线程是否有可能读取损坏的值?

我正在研究32位ARM嵌入式系统。编译器似乎总是对齐32位变量,因此可以使用单个指令读取或写入它们。如果32位变量未对齐,则读取或写入将被分解为多个指令,第二个线程可能会读取损坏的值。

如果我将来迁移到多核系统并且变量在核心之间共享,那么这个问题的答案是否会改变? (假设核心之间有共享缓存)

谢谢!


12642
2017-07-01 20:56


起源

你关心比赛,还是只关心撕裂? - David Heffernan
一旦你对自己没有发生撕裂感到满意,记住不要写 i++, 要么 i+=j 没有互斥量。 - David Heffernan
感谢关于撕裂的评论 - 我不知道它的名字是什么。我主要想知道什么是“标准”和/或什么是“良好做法”以避免撕裂和竞争条件。 - Will


答案:


互斥锁可以保护您免受撕裂 - 例如某些ARM实现使用无序执行,而互斥锁将包含可能对您的算法正确性所必需的内存(和编译器)障碍。

包含互斥锁更安全,然后找出一种方法来优化它,如果它显示为性能问题。

另请注意,如果您的编译器是基于GCC的,那么您可以访问 GCC原子内置


8
2017-07-02 04:56



感谢所有细节。读 usenix.org/event/hotpar11/tech/final_files/Boehm.pdf 说服你是对的。 - Will


如果所有的写入都是从一个线程完成的(即其他线程只是读取),那么你不需要互斥锁。如果有多个线程 可能 写作,然后你做。


4
2017-07-01 22:10





你不需要互斥。 在32位ARM上,单次写入或读取是原子操作。 (无论核心数量) 当然,您应该将该变量声明为volatile。


3
2017-07-01 21:57



对于我上面的简单案例,我认为这是有道理的。然而,David Heffernan关于撕裂的评论引发了我的注意 usenix.org/event/hotpar11/tech/final_files/Boehm.pdf。那篇论文让我对良性数据竞赛更加紧张,以及“易变性”可能不是一个完整的解决方案。 - Will


在32位系统上,32位变量的读写是原子的。但是,这取决于你对变量做了什么。例如。如果你以某种方式操纵它(例如添加一个值),那么这需要读取,操作和写入。如果CPU和编译器不支持此操作的原子操作,则需要使用互斥锁来保护此多操作序列。

还有其他无锁技术可以减少对互斥锁的需求。


1
2017-07-02 07:47



cpus是否存在非原子“移动”操作?我相信更新内存位置值的每个机器指令都是原子的,所有应该关心的只是一个线程可以读取旧值而不是更新的值(在一个线程写入的情况下,另一个线程写入当然是阅读) - ShinTakezou
@ShinTakezou - 如果32位变量未对齐,ARM将生成非原子读/写。 (即,它将执行4个字节的加载而不是字加载,以避免未对齐的访问异常) - Will
ARM还是编译器?我想由编译器来生成正确的代码;并且由于编译器“知道”它确实生成4个指令而不是单个写入的一个指令,它可以放置“障碍”以使整个写入原子并避免C程序员不得不担心硬件的细节编译源代码(可能必须有一个选项来禁用这些障碍,但对我来说,默认情况下应添加它们,如果有可能的话,我认为它应该是) - ShinTakezou
对不起 - 我的意思是编译器。例如,在ARM系统上,GCC将为对齐的32位加载生成类似以下程序集的内容: ldr r0, [r0, #7] 对于未对齐的32位加载,类似于以下内容: ldrb r2, [r0, #8] ldrb r3, [r0, #7] ldrb r1, [r0, #9] orr r3, r3, r2, lsl #8 ldrb r2, [r0, #10] orr r3, r3, r1, lsl #16 orrs r2, r3, r2, lsl #24。 GCC不会为此代码添加任何类型的锁定。 - Will