【发布时间】:2011-09-28 16:52:27
【问题描述】:
我在多线程应用程序中使用 gcc 原子操作已经有一段时间了,昨天遇到了一个我无法解释的有趣场景。这些原子函数是重载的,可以使用 1、2、4 或 8 字节宽的数据类型。特别是,我成功地使用了 bool_compare_and_swap (CAS) 操作。这里出现的问题是可重复的,并且仅在我编译优化代码 (O3) 时发生。当我编译未优化(O0)时,不会出现此问题。请记住这一点,因为我相信优化器在我在这里展示的情况下正在做一些时髦的事情。
我通常做的是创建一个包含命名类型(chars、shorts 等)和数据类型的联合,该数据类型是“适合”该结构到一个对象中的适当大小(即 long long),在这种情况下,我在一个联合中有一个 8 字节(long long),其对应的结构包含位域。示例数据类型定义如下所示。这个想法是位域可以通过赋值语句来改变,一旦所有的赋值完成,8字节的数据类型就是那个被CAS'd的数据类型。具体来说,我正在使用:
bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)
这样封装在宏中:
define THD_CAS(ptr, oldVal, newVal) __sync_bool_compare_and_swap(ptr, oldVal, newVal)
我有一个定义如下的结构:
typedef union _TSynchro {
struct {
int *pFirstSynchWork;
unsigned short idTransaction;
unsigned short fNewTrans :1,
fFileBad :1,
fOpComplete :1,
fCancelWork :1,
fPurged :1,
fStatRequired :1;
} Data;
// above struct is overlayed by this struct so we can CAS all values with a single 64 bit cas
long long n64;
} TSynchro;
所以,我在代码中有一个并发循环(while(1)),它获取当前数据值的“快照”并存储在旧数据中,在数据的新副本(新)中设置位,然后尝试CAS操作。如果 CAS 成功,我就是更改数据的线程,我会跳出循环。如果 CAS 失败,则其他线程更改了我下面的数据,然后我重试,抓取当前数据的另一个“快照”。
void NewSynchro(TSynchro *pSynchro)
{
volatile TSynchro New;
volatile TSynchro Old;
while (1) // concurrency loop
{
Old.n64 = pSynchro->n64;
New.n64 = Old.n64;
New.Data.fOpComplete = 1;
New.Data.fStatRequired = 0;
if (fFileBad)
{
New.Data.fFileBad = 1;
}
else
{
New.Data.fReleased = 0;
New.Data.fFileBad = 0;
}
if (THD_CAS(&pSynchro->n64, Old.n64, New.n64))
break; // success
}
}
现在,有趣的是...看到我将旧的和新的声明为 volatile 了吗?好吧,如果旧版和新版都没有 volatile 修改,那么当我在调用 NewSynchro() 之后进入下一个函数调用时,我会得到一个 SEGV。如果 OLD 或 NEW 或 BOTH 都有 volatile 修饰符,则应用程序代码永远不会 SEGV。在开发中,我现在只运行 1 个线程(没有真正的更改值的争用威胁),所以我还尝试摆脱 CAS 并用一个简单的赋值替换(即 pSynchro->n64 = New.n64 ),并且应用程序也运行良好。
我一直在其他地方使用 8 字节 CAS,它似乎工作正常。这里的一个区别是我认为这是我第一次在结构中使用位域。
想法?
【问题讨论】: