因此,我们现在使用的Boost版本很旧,在升级之前,我需要
为我的代码在C++中进行原子CAS操作。 (我们也没有使用C++ 0x)

我创建了以下cas函数:

inline uint32_t CAS(volatile uint32_t *mem, uint32_t with, uint32_t cmp)
{
    uint32_t prev = cmp;
    // This version by Mans Rullgard of Pathscale
    __asm__ __volatile__ ( "lock\n\t"
            "cmpxchg %2,%0"
            : "+m"(*mem), "+a"(prev)
              : "r"(with)
                : "cc");

    return prev;
}

我使用该功能的代码如下:
void myFunc(uint32_t &masterDeserialize )
{
    std::ostringstream debugStream;

    unsigned int tid = pthread_self();
    debugStream << "myFunc, threadId: " << tid << " masterDeserialize= " << masterDeserialize << " masterAddress = " << &masterDeserialize << std::endl;

    // memory fence
    __asm__ __volatile__ ("" ::: "memory");
    uint32_t retMaster = CAS(&masterDeserialize, 1, 0);
    debugStream << "After cas, threadid = " << tid << " retMaster = " << retMaster << " MasterDeserialize = " << masterDeserialize << " masterAddress = " << &masterDeserialize << std::endl;
    if(retMaster != 0) // not master deserializer.
    {
       debugStream << "getConfigurationRowField, threadId: " << tid << " NOT master.  retMaster = " << retMaster << std::endl;

       DO SOMETHING...
    }
    else
    {
        debugStream << "getConfigurationRowField, threadId: " << tid << " MASTER. retMaster = " << retMaster << std::endl;

        DO SOME LOGIC

        // Signal we're done deserializing.
        masterDeserialize = 0;
    }
    std::cout << debugStream.str();
}

我对此代码的测试产生了10个线程,并用相同的masterDeserialize信号通知所有这些线程调用该函数。

在大多数情况下,这种方法效果很好,但是每隔两千至两百万次测试迭代一次,两个线程都可以进入获取MASTER锁的路径。

我不确定这怎么可能,或如何避免。

我尝试在重置masterDeserialize之前使用内存防护,以为cpu OOO可能会产生影响,但这对结果没有影响。

显然,它可以在具有许多内核的计算机上运行,​​并且是在 Debug模式下编译的,因此GCC不应重新排序执行以进行优化。

关于上述问题有什么建议吗?

编辑:
我尝试使用gcc原语而不是汇编代码,得到了相同的结果。
inline uint32_t CAS(volatile uint32_t *mem, uint32_t with, uint32_t cmp)
{
    return __sync_val_compare_and_swap(mem, cmp, with);
}

我在多核,多cpu机器上运行,但是它是虚拟机,是否可能是VM导致了此行为?

最佳答案

从理论上讲,不仅两个线程而且任何数量的线程都可以成为此代码的“主”。问题在于,在完成后采用主路径的线程会将masterDeserialize变量设置回0,因此可以通过可能到达CAS时间很晚的线程(例如,由于抢占)再次“获取”。

解决方法实际上很简单-将第三个状态(例如,值为2)添加到该标志以表示“主服务器已完成”,并在主服务器路径的末尾使用此状态(而不是初始状态0)。表示其工作已完成。因此,只有一个调用myFunc的线程可以看到0,这为您提供了所需的保证。要重用该标志,您需要将其显式重新初始化为0。

08-06 14:53