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

原文链接 , 译文链接,  译者: dabaosod011 ,校对:梁海舰

其他一些例子

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

举例来说,考虑如下程序,x和y是两个普通变量,初始化值均为0:

线程 1

线程2
if (x != 0)
  y = 1;

y = 2;

这个例子并不会导致数据竞争的出现:x变量永远都是0,因此线程1的 y = 1语句永远都不会执行。

对于下面这个例子也同样如此:

线程 1

线程2

if (x != 0)
  y = 1;

if (y != 0)
  x = 1;

跟上面例子一样,这两个线程同样不存在数据竞争的问题。

(这两个例子如果按照POSIX标准来看并不那么清晰,是否存在数据竞争会有一些争议。我们坚持认为不存在的理由就是考虑到,数据竞争只有在顺序一致性的执行过程中才会发生)

(译者注:POSIX强调的是,如果上述程序的执行结果依赖于两个线程的交叉执行顺序,则存在数据竞争)

然而,如果我们稍微改变一下第一个例子,就会导致数据竞争的出现了:

线程 1

线程2

y = ((x != 0)? 1 : y)

y = 2;

尽管在单线程运行时,上述两个例子的线程1 是等价的。但在多线程运行时就不一样了,在新版本的例子中y的值需要依情况而定。在特定情况下,y的值有可能在线程2执行的同时被改写。由于线程1的代码往往会被分割为几步执行,这种数据竞争隐含着很容易出现的问题,如果上述代码按照如下的顺序执行:

tmp = ((x != 0)? 1 : y);   // tmp = 0
y = 2;
y = tmp;   // y = 0

这将导致非预期性的结果,而这在最初的例子中是不会出现的。因此编译器不能以这种方式去生成代码,尽管硬件的条件转移指令会让这种代码执行速度更快,或者可以使循环语句被矢量化(译者注:https://wiki.engr.illinois.edu/download/attachments/114688007/9-Vectorization.pdf)。如果需要这种快速的版本,要么明确地按照程序的执行顺序生成代码,要么需要以某种形式让编译器能够决定哪些变量不会被共享访问。

一些其他常见的编译器优化技术也会潜在的引入数据竞争。例如考虑下面这个循环语句,用来统计list中负数的数量,其中count是一个全局或者静态类成员:

 void count_neg(T *q)
 {
    for (T*p = q; p != 0; p = p -> next;)
    {
        if (p -> data < 0) ++count;
    }
 }

这段代码很明显会引入数据竞争,如果当前线程正在更新count值的同时,有另一个线程也刚好访问到这个变量。

编译器有可能会将count存放在某个临时变量里,然后将代码转换为以下形式:

 void count_neg(T *q)
 {
     int local_count = count;

     for (p = q; p != 0; p = p -> next;)
     {
        if (p -> data < 0) ++local_count;
     }
     count = local_count;
 }

但这也会引入数据竞争。假设pos_list指向某个list,它包含两个data值:1和2, 然后声明count为全局变量并初始化为0。接下来有两个线程分别执行:

线程 1

线程2

count_neg(pos_list);

count++;

先前没有使用临时变量版本的程序不会存在数据竞争,因为pos_list并不包含负数,因此线程1不会读也不会改写全局变量count。

(有些人可能会觉得关于数据竞争的这种定义,没有必要苛刻要求按照特定的执行顺序,因此这就需要考虑传递给函数的一些特定的参数。尤其是在实际程序中需要传递指针参数时,这是唯一讲得通的定义,否则任何通过指针来写数据的函数都会引入数据竞争了。)

上面这个被编译器优化后的程序(引入临时变量local_count),线程1不管何时都存在对count的写操作,因此也就引入了数据竞争。

(尽管在Java和C++0x中都将这种转换清楚地列为非法,然而在POSIX中却没有明确规定,而且在当前(2008年)许多C编译器中都存在这种转换。)

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

 void count_neg(T *q)
 {
    int local_count = 0;

    for (p = q; p != 0; p = p -> next;)
    {
        if (p -> data < 0) ++local_count;
    }
    if (local_count != 0) count += local_count;
 }

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 线程基础之“其他一些例子”

Latest posts by dabaosod011 (see all)

FavoriteLoading添加本文到我的收藏
  • Trackback are closed
  • Comments (0)
  1. No comments yet.

You must be logged in to post a comment.

return top