当前位置: C语言 -- 基础 -- 多线程执行和数据竞争

多线程执行和数据竞争(四)


3、携带依赖

“携带依赖(carries a dependency)”关系是“之前排序(sequenced before)”关系的子集,同样严格地限定在线程内。对于两个评估AB,如果满足下述条件之一,则评估A携带依赖于评估B

-- A的值用作B的操作数,但这些情况除外:

B是对宏kill_dependency的调用。

A是逻辑与(&&)或者逻辑或(||)运算符的左操作数。

A是三元运算符(?:)的左操作数。

A是逗号运算符(,)的左操作数。

-- A向标量对象或者位字段M写入值,BM读取A写入的值,并且AB之前排序。

-- 对于某个评估XA携带依赖于X,并且X携带依赖于B


以下是两个关于表达式评估排序的例子:

x = 2, y = 3;
sum = f1(3) + f2(5);

C语言中子表达式的评估顺序和副作用的发生顺序是未指定行为,但涉及函数调用、逻辑与运算符(&&)、逻辑或运算符(||)、三元运算符(?:)、逗号运算符(,)的情况除外。

逗号运算符左操作数的评估和右操作数的评估之间存在一个序列点(sequence point),因此子表达式x = 2在子表达式y = 3之前排序。

函数f1、函数f2可以以任意顺序调用;但在执行加法运算前其所有副作用应完成。


以下是一个携带依赖的例子:

atomic_int aNumber = 0;
...
atomic_store(&aNumber, 5);
atomic_fetch_add(&aNumber, 1);

表达式atomic_store(&aNumber, 5)向原子对象&aNumber写入5;表达式atomic_fetch_add(&aNumber, 1)将原子对象&aNumber的值加1;表达式atomic_store(&aNumber, 5)携带依赖于表达式atomic_fetch_add(&aNumber, 1)


4、前序依赖

“前序依赖(dependency-ordered before)”关系类似于“与同步(synchronize with)”关系,不同的是使用释放/消费(release/consume)来代替释放/获取(release/acquire)。对于两个评估AB,如果满足下述条件之一,则评估A前序依赖于评估B

-- A对原子对象M执行释放操作(release operation),另一个线程中BM执行消费操作(consume operation),并且读取以A为首的释放序列中任何副作用写入的值。

-- 对于某个评估XA前序依赖于X,并且X携带依赖于B


以下是一个前序依赖的例子:

atomic_int x = 0;
int i = 0;

/*线程一中执行的函数。*/
int f1(void *arg)
{
    atomic_store_explicit(&x, 5, memory_order_release);    //A

    thrd_exit(0);
}

/*线程二中执行的函数。*/
int f2(void *arg)
{
    int temp = 0;
    
    while(!(temp = atomic_load_explicit(&x, memory_order_consume)))   //B
      ;
    i = temp;    

    thrd_exit(0);
}

线程一A处的表达式atomic_store_explicit(&x, 5, memory_order_release)对原子对象x执行释放操作;线程二B处的表达式atomic_load_explicit(&x, memory_order_consume)对原子对象x执行消费操作。


5、线程间之前发生

“线程间之前发生(inter-thread happens before)”关系描述了“之前排序(sequenced before)”、“与同步(synchronize with)”、“前序依赖(dependency-ordered before)”关系的任意串联,但存在两个例外:

-- 第一个例外:串联不能以“前序依赖(dependency-ordered before)”后跟“之前排序(sequenced before)”结尾。

参与“前序依赖”关系的消费操作只对该消费操作存在依赖关系的操作进行排序。这种限制只适用于这种串联的末端,原因是任何后续的释放操作都将为之前的消费操作提供所需的排序。

-- 第二个例外:串联不能完全由“之前排序(sequenced before)”组成。

作出这一限制的原因是:允许“线程间之前发生”被临时关闭;“之前发生(happens before)”关系包含了完全由“之前排序”组成的关系。


对于两个评估AB,如果满足下述条件之一,则评估A线程间发生在评估B之前:

-- 评估A与评估B同步。

-- 评估A前序依赖于评估B

-- 对于某个评估X,评估A与评估X同步,并且评估X在评估B之前排序。

-- 对于某个评估X,评估A在评估X之前排序,并且评估X线程间发生在评估B之前。

-- 对于某个评估X,评估A线程间发生在评估X之前,并且评估X线程间发生在评估B之前。


如果评估A在评估B之前排序,或者评估A线程间发生在评估B之前,则评估A在评估B之前发生(happens before)。


以下是一个线程间之前发生的例子:

atomic_int x = 0;
int i = 0;

/*线程一。*/
...
atomic_store_explicit(&x, 5, memory_order_release);
...

/*线程二。*/
int temp = 0;
while(!(temp = atomic_load_explicit(&x, memory_order_acquire)))
;
i = temp;

线程一中的释放操作atomic_store_explicit(&x, 5, memory_order_release)与线程二中的获取操作atomic_load_explicit(&x, memory_order_acquire)同步,所以表达式atomic_store_explicit(&x, 5, memory_order_release)的评估在表达式i = temp的评估之前发生。