C 互斥解锁是否起到内存隔离的作用?
我将描述的情况发生在iPad4(ARMv7s)上,使用posix libs进行互斥锁定/解锁。不过,我在其他ARMv7设备上也看到过类似的情况(见下文),因此我认为任何解决方案都需要更全面地了解ARMv7互斥体和内存围栏的行为 场景的伪代码: 线程1–生成数据:C 互斥解锁是否起到内存隔离的作用?,c,ipad,arm,mutex,memory-barriers,C,Ipad,Arm,Mutex,Memory Barriers,我将描述的情况发生在iPad4(ARMv7s)上,使用posix libs进行互斥锁定/解锁。不过,我在其他ARMv7设备上也看到过类似的情况(见下文),因此我认为任何解决方案都需要更全面地了解ARMv7互斥体和内存围栏的行为 场景的伪代码: 线程1–生成数据: void ProduceFunction() { MutexLock(); int TempProducerIndex = mSharedProducerIndex; // Take a copy of the int memb
void ProduceFunction() {
MutexLock();
int TempProducerIndex = mSharedProducerIndex; // Take a copy of the int member variable for Producers Index
mSharedArray[TempProducerIndex++] = NewData; // Copy new Data into array at Temp Index
mSharedProducerIndex = TempProducerIndex; // Signal consumer data is ready by assigning new Producer Index to shared variable
MutexUnlock();
}
void ConsumingFunction () {
while (mConsumerIndex != mSharedProducerIndex) {
doWorkOnData (mSharedArray[mConsumerIndex++]);
}
}
线程2–消耗数据:
void ProduceFunction() {
MutexLock();
int TempProducerIndex = mSharedProducerIndex; // Take a copy of the int member variable for Producers Index
mSharedArray[TempProducerIndex++] = NewData; // Copy new Data into array at Temp Index
mSharedProducerIndex = TempProducerIndex; // Signal consumer data is ready by assigning new Producer Index to shared variable
MutexUnlock();
}
void ConsumingFunction () {
while (mConsumerIndex != mSharedProducerIndex) {
doWorkOnData (mSharedArray[mConsumerIndex++]);
}
}
以前(当问题出现在iPad2上时),我认为mSharedProducerIndex=TempProducerIndex
不是以原子方式执行的,因此改为使用AtomicCompareAndSwap
分配mSharedProducerIndex
。这一直到现在,但事实证明我错了,错误又回来了。我猜“修复”只是改变了一些时间
我现在得出的结论是,实际问题是互斥锁内写入操作的无序执行,即如果编译器或硬件决定重新排序:
mSharedArray[TempProducerIndex++] = NewData; // Copy new Data into array at Temp Index
mSharedProducerIndex = TempProducerIndex; // Signal consumer data is ready by assigning new Producer Index to shared variable
。。。致:
mSharedProducerIndex = TempProducerIndex; // Signal consumer data is ready by assigning new Producer Index to shared variable
mSharedArray[TempProducerIndex++] = NewData; // Copy new Data into array at Temp Index
。。。然后消费者将生产者交错,当消费者试图读取数据时,数据还没有被写入
因此,在阅读了一些关于内存屏障的内容后,我认为我应该尝试将信号移动到互斥锁(mutex_unlock
)之外的消费者,相信解锁会产生一个内存屏障/屏障,从而确保mSharedArray
已写入:
mSharedArray[TempProducerIndex++] = NewData; // Copy new Data into array at Temp Index
MutexUnlock();
mSharedProducerIndex = TempProducerIndex; // Signal consumer data is ready by assigning new Producer Index to shared variable
然而,这仍然是失败的,这让我怀疑互斥锁解锁
是否一定会起到写围栏的作用
我还读过一篇文章,其中建议编译器可以将代码移入(但不能移出)crit_sec
s。因此,即使在上述更改之后,mSharedProducerIndex
的写入也可能在屏障之前。这个理论有什么意义吗
通过添加明确的围栏,问题就消失了:
mSharedArray[TempProducerIndex++] = NewData; // Copy new Data into array at Temp Index
OSMemoryBarrier();
mSharedProducerIndex = TempProducerIndex; // Signal consumer data is ready by assigning new Producer Index to shared variable
因此,我认为我理解这个问题,并且需要一个围栏,但是任何关于解锁行为的洞察,以及为什么它看起来没有执行屏障的洞察,都将非常有用
编辑:
void ProduceFunction() {
MutexLock();
int TempProducerIndex = mSharedProducerIndex; // Take a copy of the int member variable for Producers Index
mSharedArray[TempProducerIndex++] = NewData; // Copy new Data into array at Temp Index
mSharedProducerIndex = TempProducerIndex; // Signal consumer data is ready by assigning new Producer Index to shared variable
MutexUnlock();
}
void ConsumingFunction () {
while (mConsumerIndex != mSharedProducerIndex) {
doWorkOnData (mSharedArray[mConsumerIndex++]);
}
}
关于消费者线程中缺少互斥体的问题:我依赖于int mSharedProducerIndex
的编写是一条指令,因此希望消费者能够读取新值或旧值。这两种状态都是有效状态,如果mSharedArray
是按顺序写入的(即在写入mSharedProducerIndex
之前),这是可以的,但从目前为止所说的情况来看,我无法对此作出答复
根据相同的逻辑,当前屏障解决方案似乎也存在缺陷,因为mSharedProducerIndex
写入可能会移动到屏障内部,因此可能会错误地重新排序
是否建议向使用者添加一个互斥锁,只是作为一个读屏障,或者是否有pragma
或指令用于禁用生产者上的无序执行,如在PPC上?理论是正确的,写入操作可以从写入栅栏后移到写入栅栏前
代码的根本问题是线程2中根本没有同步。您阅读
mSharedProducerIndex
时没有阅读障碍,因此谁知道您将获得什么价值。您在线程1中所做的任何操作都不能解决这个问题。您的产品是同步的,但您在消费时不进行任何同步(您还需要将内存与屏障同步)。所以,即使你有完美的记忆障碍,为生产者记忆障碍不会帮助消费者
在您的代码中,编译器的排序、硬件排序甚至是运行线程2的其他核心上的过时值mSharedProducerIndex
都会影响到您
您应该阅读第11章:内存顺序
,特别是11.2.1内存屏障使用示例
我认为你的问题是你在消费者线程中得到了部分更新。问题是在生产者的关键部分内的东西不是原子的,它可以被重新排序
通过非原子的我的意思是如果你的mSharedArray[TempProducerIndex++]=NewData
不是一个字存储(NewData的类型为int),它可以通过几个步骤完成,其他核心可以将这些步骤视为部分更新
通过重新排序
我的意思是互斥提供了进出障碍,但在关键部分不强制任何排序。由于用户端没有任何特殊构造,您可以看到mSharedProducerIndex
已更新,但仍然可以看到mSharedArray[mConsumerIndex]
的部分更新。互斥仅在执行离开关键部分后保证内存可见性
我相信这也解释了为什么添加OSMemoryBarrier()代码>在临界区内,因为这样cpu被迫将数据写入mSharedArray
,然后更新mConsumerIndex
,当其他内核/线程看到mConsumerIndex
时,我们知道mSharedArray
由于障碍被完全复制
我认为您使用OSMemoryBarrier()实现假设您有多个生产者和一个消费者,代码>是正确的。我不同意任何建议在消费者中设置内存障碍的评论,因为我相信这不会修复生产者内部关键部分发生的部分更新或重新排序
作为对您标题中问题的回答,通常情况下,afaikmutex
es在进入屏障之前已经读过屏障,在离开屏障之后写入屏障。抱歉,我过于简化了我的伪代码,将在几秒钟内进行编辑。不过,为现在的答案干杯。编辑完成了,希望它能澄清情况。干杯:)感谢您迄今为止的投入,我对这个问题进行了编辑,希望能澄清一点。@sjwarner如果mSharedArray不是一个int数组,我想您可以