mutex_unlock是否可用作内存栅栏?

sam*_*m-w 7 c mutex arm memory-barriers ipad

我将描述的情况发生在iPad 4(ARMv7s)上,使用posix libs进行互斥锁定/解锁.我在其他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();
}
Run Code Online (Sandbox Code Playgroud)

线程2 - 消费数据:

void ConsumingFunction () {
  while (mConsumerIndex != mSharedProducerIndex) {
    doWorkOnData (mSharedArray[mConsumerIndex++]);
  }
}
Run Code Online (Sandbox Code Playgroud)

以前(当问题出现在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
Run Code Online (Sandbox Code Playgroud)

... 至:

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 
Run Code Online (Sandbox Code Playgroud)

...然后消费者交错了生产者,当消费者试图读取数据时,数据还没有被写入.

经过对记忆障碍的一些阅读后,我认为我会尝试将信号传递给外面的消费者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
Run Code Online (Sandbox Code Playgroud)

然而,这仍然失败了,并引导我质疑一个mutex_unlock肯定是否会成为写围栏?

我还阅读了惠普的一篇文章,该文章表明编译器可以将代码移入(但不会移出)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
Run Code Online (Sandbox Code Playgroud)

因此,我认为我理解这个问题,并且需要一个围栏,但是对解锁行为以及为什么它似乎没有执行障碍的任何洞察都会非常有用.

编辑:

关于消费者线程中缺少互斥锁:我依赖于写入int mSharedProducerIndex单个指令,因此希望消费者能够读取新值或旧值.要么是有效状态,要么提供mSharedArray按顺序写入(即在写入之前mSharedProducerIndex),这样就可以了,但是从目前为止所说的内容来看,我无法对此做出回复.

通过相同的逻辑,似乎当前的屏障解决方案也存在缺陷,因为mSharedProducerIndex写入可能在屏障内移动,因此可能被错误地重新排序.

是否建议将消费者添加到消费者,只是作为阅读障碍,或者是否存在pragma禁止生产者无序执行的指令或指令,如EIEIOPPC?

Ste*_*sop 5

"理论"是正确的,写入可以在写入栅栏之后移动到它之前.

与您的代码最根本的问题是,有在所有线程2.你读不同步mSharedProducerIndex,而不读不通,所以谁知道珍惜,你会得到.你在线程1中做的任何事情都无法解决这个问题.


aus*_*len 5

你的产品是同步的,但你没有做任何同步(你需要同步内存与障碍)消费.所以,即使你对生产者有完美的记忆障碍,记忆障碍也无法帮助消费者.

在您的代码中,您可能会遇到编译器的排序,硬件排序甚至是mSharedProducerIndex其他核心运行的线程#2上的陈旧值.

你应该阅读Chapter 11: Memory Ordering皮质™-A系列程序员指南,尤其是11.2.1 Memory barrier use example.

我认为您的问题是您正在获得消费者线程的部分更新.问题是生产者内部关键部分的内容不是原子的,可以重新排序.

通过not atomic我的意思是,如果你mSharedArray[TempProducerIndex++] = NewData;不是一个字店(NewData为int类型)可能在其中可以通过其他核心的部分更新可以看出以下几个步骤来完成.

通过reordering我的意思是互斥提供壁垒和退出,但关键部分在不强加任何顺序.由于您在消费者方面没有任何特殊构造,因此您可以看到mSharedProducerIndex已更新但仍会看到部分更新mSharedArray[mConsumerIndex].Mutex仅在执行离开关键部分后才保证内存可见性.

我相信这也解释了为什么它在你添加OSMemoryBarrier();内部临界区时有效,因为这样cpu被强制写入数据mSharedArray然后更新mConsumerIndex,当其他核心/线程看到mConsumerIndex我们知道mSharedArray由于障碍而被完全复制时.

我认为你的实施OSMemoryBarrier();是正确的,假设你有许多生产者和一个消费者.我不同意把建议内存屏障消费者任何意见,因为我相信这不会解决部分更新或内部生产关键部分重新排序发生.

作为对题目问题的回答,一般来说,在mutex他们离开之前进入并写入障碍之前,他们已经阅读了障碍.