互斥操作函数参数应该是“volatile”吗?

Should the mutex manipulation functions parameter be `volatile`?

我正在使用自定义嵌入式实时 OS,它具有一些自制的线程和同步工具。互斥的实现方式类似于下面概述的方式:

typedef int Mutex;
#define MUTEX_ACQUIRED    1
#define MUTEX_RELEASED    0

bool AquireMutex(Mutex* pMutex)
{
    bool Ret;
    // Assume atomic section here (implementation specific)
    if( *pMutex == MUTEX_RELEASED )
    {
         *pMutex = MUTEX_ACQUIRED;
         Ret = true;
    } 
    else
    {
         Ret = false;
    }
    // Atomic section end
    return Ret;
}

void ReleaseMutex(Mutex* pMutex)
{
    // Assume atomic section here (implementation specific)
    *pMutex = MUTEX_RELEASED;
    // end atomic section
}

让我们假设上面的两个函数是原子的(在实际实现中它们是,但实际实现与问题无关)。

每个线程都共享一些全局定义的 m 并且具有类似于以下的代码:

extern Mutex m;
// .............
while (!AquireMutex(&m)) ;
// Do stuff
ReleaseMutex(&m);

问题是关于这条线的:

while (!AquireMutex(&m)) ;

AquireMutex 是否会在每次迭代时实际进行评估?或者优化器只会将其视为常量,因为它看不到 m 是如何更改的? 是否应该使用 volatile 限定符来声明 AquireMutex

bool AquireMutex(volatile Mutex* pMutex);

答案取决于使用 while 循环的调用站点是否可以看到这些函数的实现。如果不是(调用站点只看到声明,定义在单独的源文件中),那么 volatile 关键字将不会改变任何内容。优化器完全不知道这个函数对参数做了什么,它是否有副作用等等,所以每次函数调用都会进行。

另一方面,如果释放和获取互斥锁的函数是内联的——那么完整实现在调用站点上是可见的——那么实际上优化器可能"tweak" 事情有点多了。问题出在 "complete" 字中 - 即使您发布的代码是内联的,开始和结束关键部分的代码也可能不是。即使是,它也可能使用优化器无法理解的汇编语句。即使它是纯 C,它也可能访问一些易失性内存映射寄存器。任何此类代码(调用外部函数、汇编语句、访问易失性内存)有效地禁止优化器消除所有调用,因为在这种情况下,它必须假设每个调用都有副作用。