这里描述的情况发生在 iPad 4 (ARMv7s)上,使用 posix 库进行 mutex 锁/解锁。我在其他 ARMv7 设备上也看到了类似的情况(见下文),因此我认为任何解决方案都需要更一般地查看 ARMv7 的 mutex 和内存屏障的行为。
以下是该场景的伪代码: 线程 1 - 生产数据:
之前(当iPad 2上出现问题时),我认为
我现在得出的结论是,在互斥锁内部写入的执行顺序是不正确的,即如果编译器或硬件决定重新排序:
然而,这仍然失败了,这让我怀疑
以下是该场景的伪代码: 线程 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
来分配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
然而,这仍然失败了,这让我怀疑
mutex_unlock
是否一定会作为写入屏障?
我也读过HP的一篇文章,其中提到编译器可以将代码移动到(但不能移出)crit_sec
,因此即使进行了上述更改,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
或指令可以禁用生产者上的乱序执行,例如 PPC 上的 EIEIO
?
mSharedProducerIndex
之后,您可以立即执行OSMemoryBarrier()。以前的测试表明,OSMemoryBarrier()比OSSpinLockLock+Unlock()要快一些,而OSSpinLockLock+Unlock()比pthread mutexes要快得多。 - tc.