C++中的比较和交换
Compare and swap in C++
所以我们使用的是目前相当旧的 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 变量调用该函数。
这在大多数情况下运行良好,但每几千到几百万次测试迭代一次,2 个线程都可以进入获取 MASTER 锁的路径。
我不确定这怎么可能,或者如何避免。
我尝试在masterDeserialize重置之前使用内存栅栏,认为cpu OOO会影响,但这对结果没有影响。
显然这在多核机器上运行,并且是在调试模式下编译的,因此 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 以某种方式引起的?
理论上不仅两个而且任意数量的线程都可以成为这段代码中的"masters"。问题在于完成后采用主路径的线程将 masterDeserialize
变量设置回 0,从而使得可能很晚到达 CAS 的线程再次 "acquire" 成为可能(例如,由于抢占).
修复实际上很简单 - 将第三个状态(例如值为 2)添加到此标志以表示 "master has completed",并在末尾使用此状态(而不是初始状态 0) master 的路径以表示其工作已完成。因此,只有一个调用 myFunc
的线程可以看到 0,这为您提供了所需的保证。要重用该标志,您需要将其显式重新初始化为 0。
所以我们使用的是目前相当旧的 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 变量调用该函数。
这在大多数情况下运行良好,但每几千到几百万次测试迭代一次,2 个线程都可以进入获取 MASTER 锁的路径。
我不确定这怎么可能,或者如何避免。
我尝试在masterDeserialize重置之前使用内存栅栏,认为cpu OOO会影响,但这对结果没有影响。
显然这在多核机器上运行,并且是在调试模式下编译的,因此 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 以某种方式引起的?
理论上不仅两个而且任意数量的线程都可以成为这段代码中的"masters"。问题在于完成后采用主路径的线程将 masterDeserialize
变量设置回 0,从而使得可能很晚到达 CAS 的线程再次 "acquire" 成为可能(例如,由于抢占).
修复实际上很简单 - 将第三个状态(例如值为 2)添加到此标志以表示 "master has completed",并在末尾使用此状态(而不是初始状态 0) master 的路径以表示其工作已完成。因此,只有一个调用 myFunc
的线程可以看到 0,这为您提供了所需的保证。要重用该标志,您需要将其显式重新初始化为 0。