mutex_unlock是否可用作内存栅栏?

时间:2013-02-21 13:24:33

标签: c ipad arm mutex memory-barriers

我将描述的情况发生在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();
}

主题2 - 使用数据:

void ConsumingFunction () {
  while (mConsumerIndex != mSharedProducerIndex) {
    doWorkOnData (mSharedArray[mConsumerIndex++]);
  }
}

之前(当问题2在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是否肯定会成为写围栏?

我还阅读了an article from 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写入可能会在屏障内移动,因此可能会错误地重新排序。

是否建议将消费者添加到消费者,只是作为阅读障碍,或者是否有{{1}}或禁止生产者执行无序执行的指令,如{{3}在PPC上?

2 个答案:

答案 0 :(得分:5)

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

您的代码的根本问题在于线程2中根本没有同步。您在没有读屏障的情况下阅读mSharedProducerIndex,因此谁知道您将获得什么价值。你在线程1中所做的一切都无法解决这个问题。

答案 1 :(得分:5)

您的产品是同步的,但您不会在消费时进行任何同步(您还需要将内存与障碍同步)。因此,即使你对生产者有完美的记忆障碍,记忆障碍也无法帮助消费者。

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

您应该从Cortex™-A Series Programmer’s Guide阅读Chapter 11: Memory Ordering,尤其是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在他们离开之前有进入和写入障碍的阅读障碍。