我将描述的情况发生在 iPad 4 (ARMv7s) 上,使用 posix 库进行互斥锁定/解锁。不过,我在其他 ARMv7 设备上也看到过类似的事情(见下文),所以我认为任何解决方案都需要更全面地了解 ARMv7 的互斥锁和内存栅栏的行为。
场景的伪代码:
线程 1 – 生产数据:
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();
}
线程 2 – 消费数据:
void ConsumingFunction () {
while (mConsumerIndex != mSharedProducerIndex) {
doWorkOnData (mSharedArray[mConsumerIndex++]);
}
}
以前(当问题出现在 iPad 2 上时),我认为这mSharedProducerIndex = TempProducerIndex
不是原子执行的,因此改为使用AtomicCompareAndSwap
to assign 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
然而,这仍然失败,并让我质疑 amutex_unlock
是否肯定会充当写栅栏?
我还阅读了 HP 的一篇文章,其中建议编译器可以将代码移入(但不能移出)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
因此,我认为我理解这个问题,并且需要围栏,但是任何对解锁行为以及为什么它似乎没有执行屏障的洞察都会非常有用。
编辑:
关于消费者线程中缺少互斥锁:我依赖于int mSharedProducerIndex
单个指令的写入,因此希望消费者能够读取新值或旧值。两者都是有效状态,并且如果mSharedArray
按顺序编写(即在编写之前mSharedProducerIndex
),这将是可以的,但从目前所说的来看,我无法对此作出答复。
按照同样的逻辑,当前的屏障解决方案似乎也存在缺陷,因为mSharedProducerIndex
写入可能会移动到屏障内,因此可能会被错误地重新排序。
是否建议向消费者添加一个互斥锁,只是作为一个读取屏障,或者是否有一个pragma
or 指令来禁用生产者上的乱序执行,比如EIEIO
在 PPC 上?