多线程执行和数据竞争(四)
3、携带依赖
“携带依赖(carries a dependency)”关系是“之前排序(sequenced before)”关系的子集,同样严格地限定在线程内。对于两个评估A和B,如果满足下述条件之一,则评估A携带依赖于评估B:
-- A的值用作B的操作数,但这些情况除外:
① B是对宏kill_dependency的调用。
② A是逻辑与(&&)或者逻辑或(||)运算符的左操作数。
③ A是三元运算符(?:)的左操作数。
④ A是逗号运算符(,)的左操作数。
-- A向标量对象或者位字段M写入值,B从M读取A写入的值,并且A在B之前排序。
-- 对于某个评估X,A携带依赖于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)。对于两个评估A和B,如果满足下述条件之一,则评估A前序依赖于评估B:
-- A对原子对象M执行释放操作(release operation),另一个线程中B对M执行消费操作(consume operation),并且读取以A为首的释放序列中任何副作用写入的值。
-- 对于某个评估X,A前序依赖于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)”关系包含了完全由“之前排序”组成的关系。
对于两个评估A和B,如果满足下述条件之一,则评估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的评估之前发生。