天天看点

线程基础之“其他一些例子” 其他一些例子

我们对数据竞争的定义相当严格:必须以确切方式去执行原始、未经转换的程序中并行的冲突操作。引入有害的数据竞争将给编译器带来无法“打断”程序的负担。

举例来说,考虑如下程序,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编译器中都存在这种转换。)

需要注意的是,至少在没有异常出现的情况下,上述代码可允许被按如下方式转换: