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

    xiaoxiao2024-07-24  37

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

    其他一些例子

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

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

    线程 1

    线程2if (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
    最新回复(0)