我们对数据竞争的定义相当严格:必须以确切方式去执行原始、未经转换的程序中并行的冲突操作。引入有害的数据竞争将给编译器带来无法“打断”程序的负担。
举例来说,考虑如下程序,x和y是两个普通变量,初始化值均为0:
线程 1
线程2
if (x != 0)
y = 1;
y = 2;
这个例子并不会导致数据竞争的出现:x变量永远都是0,因此线程1的 y = 1语句永远都不会执行。
对于下面这个例子也同样如此:
if (y != 0)
x = 1;
跟上面例子一样,这两个线程同样不存在数据竞争的问题。
(这两个例子如果按照posix标准来看并不那么清晰,是否存在数据竞争会有一些争议。我们坚持认为不存在的理由就是考虑到,数据竞争只有在顺序一致性的执行过程中才会发生)
(译者注:posix强调的是,如果上述程序的执行结果依赖于两个线程的交叉执行顺序,则存在数据竞争)
然而,如果我们稍微改变一下第一个例子,就会导致数据竞争的出现了:
y = ((x != 0)? 1 : y)
尽管在单线程运行时,上述两个例子的线程1 是等价的。但在多线程运行时就不一样了,在新版本的例子中y的值需要依情况而定。在特定情况下,y的值有可能在线程2执行的同时被改写。由于线程1的代码往往会被分割为几步执行,这种数据竞争隐含着很容易出现的问题,如果上述代码按照如下的顺序执行:
一些其他常见的编译器优化技术也会潜在的引入数据竞争。例如考虑下面这个循环语句,用来统计list中负数的数量,其中count是一个全局或者静态类成员:
这段代码很明显会引入数据竞争,如果当前线程正在更新count值的同时,有另一个线程也刚好访问到这个变量。
编译器有可能会将count存放在某个临时变量里,然后将代码转换为以下形式:
但这也会引入数据竞争。假设pos_list指向某个list,它包含两个data值:1和2, 然后声明count为全局变量并初始化为0。接下来有两个线程分别执行:
count_neg(pos_list);
count++;
先前没有使用临时变量版本的程序不会存在数据竞争,因为pos_list并不包含负数,因此线程1不会读也不会改写全局变量count。
(有些人可能会觉得关于数据竞争的这种定义,没有必要苛刻要求按照特定的执行顺序,因此这就需要考虑传递给函数的一些特定的参数。尤其是在实际程序中需要传递指针参数时,这是唯一讲得通的定义,否则任何通过指针来写数据的函数都会引入数据竞争了。)
上面这个被编译器优化后的程序(引入临时变量local_count),线程1不管何时都存在对count的写操作,因此也就引入了数据竞争。
(尽管在java和c++0x中都将这种转换清楚地列为非法,然而在posix中却没有明确规定,而且在当前(2008年)许多c编译器中都存在这种转换。)
需要注意的是,至少在没有异常出现的情况下,上述代码可允许被按如下方式转换: