我知道C标准并不保证数据竞争存在的任何事情(我相信数据竞争具有未定义的行为,意味着任何事情都会发生,包括程序终止,修改随机内存等等) .
是否有任何架构,其中包含一个线程写入内存位置的数据争用和一个从同一位置读取的线程(没有同步)不会导致读取操作读取未定义的值以及内存位置“最终”(在内存屏障之后)更新为写操作写入的值?
[编辑以“数据竞赛”取代“竞争条件”]
数据争用的问题不在于,您可以在计算机级别读取错误的值 . 数据竞争的问题在于,编译器和处理器都对代码执行了大量优化 . 为了确保在存在多个线程时这些优化是正确的,它们需要有关可在线程之间共享的变量的其他信息 . 这样的优化可以例如:
重新排序操作
添加额外的加载和存储操作
删除加载和存储操作
汉斯·博姆(Hans Boehm)的良好纸质良性数据竞赛名为How to miscompile programs with "benign" data races . 以下摘录摘自本文:
对延迟初始化进行双重检查众所周知,这在源代码级别是不正确的 . 典型的用例看起来像if(!init_flag){锁();if(!init_flag)开锁();}tmp = my_data;没有什么能阻止优化编译器将my_data的设置与init_flag的设置重新排序,或者甚至将my_data的加载推进到init_flag的第一次测试之前,如果未设置init_flag则在条件中重新加载它 . 即使编译器不执行转换,某些非x86硬件也可以执行类似的重新排序 . 这些中的任何一个都可能导致my_data的最终读取看到未初始化的值并产生不正确的结果 .
这是另一个例子,其中 int x 是共享的, int r 是局部变量 .
int x
int r
int r = x; if (r == 0) printf("foo\n"); if (r != 0) printf("bar\n");
如果我们只是说,读取 x 会导致未定义的值,那么程序将打印"foo"或"bar" . 但是如果编译器按如下方式转换代码,程序也可能会打印两个字符串或者不打印任何字符串 .
x
if (x == 0) printf("foo\n"); if (x != 0) printf("bar\n");
你可以使用Linux操作系统,你可以在c中通过父进程分叉2个或更多的子进程,你可以使两者都访问一个内存位置,通过使用同步你可以实现你想做的事.--> How to share memory between process fork()?,http://en.wikipedia.org/wiki/Dekker 's_algorithm,http://en.wikipedia.org/wiki/Readers%E2%80%93writers_problem,
一个总会导致竞争位置的示例:要求两个线程将不同的值写入 same 变量 . 让我们假设一下
thread one将变量a设置为1
thread两个将变量a设置为2
你会得到 race condition ,即使有一个互斥,例如因为
如果先执行第一个线程,则得到a = 1,然后a = 2 .
如果先执行第二个线程,则得到a = 2,然后a = 1 .
线程的顺序取决于操作系统,并且没有关于首先使用哪个线程的保证 . 否则它将是顺序的,不需要在单独的线程中执行它 .
现在假设您根本没有同步,并且您在第一个线程中执行a = a 1,在第二个线程中执行a = a 2 . a的初始值为0 .
在汇编中,生成的代码是将a的值复制到一个寄存器中,向它添加1(在第一个线程的情况下,否则为2) .
如果您有 no synchronization at all ,则可以按照以下顺序进行操作
Thread1:复制到reg1的值 . reg1包含0
Thread2:复制到reg2的值 . reg2包含0
Thread1:添加了reg1的值1.现在包含1
Thread2:添加了reg2的值2.现在包含2
Thread1:reg1的值放到a . 现在一个包含1
Thread2:reg2的值放到a . 现在一个包含2
如果你执行了thread1,那么 sequentially thread 2你最后会有一个= 3 .
现在假设a是一个指针,即一个adressm,因为你知道,得到一个错误的指针地址会导致程序崩溃 . 因此错误的同步可能导致程序 crash .
说得通?
3 回答
数据争用的问题不在于,您可以在计算机级别读取错误的值 . 数据竞争的问题在于,编译器和处理器都对代码执行了大量优化 . 为了确保在存在多个线程时这些优化是正确的,它们需要有关可在线程之间共享的变量的其他信息 . 这样的优化可以例如:
重新排序操作
添加额外的加载和存储操作
删除加载和存储操作
汉斯·博姆(Hans Boehm)的良好纸质良性数据竞赛名为How to miscompile programs with "benign" data races . 以下摘录摘自本文:
这是另一个例子,其中
int x
是共享的,int r
是局部变量 .如果我们只是说,读取
x
会导致未定义的值,那么程序将打印"foo"或"bar" . 但是如果编译器按如下方式转换代码,程序也可能会打印两个字符串或者不打印任何字符串 .你可以使用Linux操作系统,你可以在c中通过父进程分叉2个或更多的子进程,你可以使两者都访问一个内存位置,通过使用同步你可以实现你想做的事.--> How to share memory between process fork()?,http://en.wikipedia.org/wiki/Dekker 's_algorithm,http://en.wikipedia.org/wiki/Readers%E2%80%93writers_problem,
一个总会导致竞争位置的示例:要求两个线程将不同的值写入 same 变量 . 让我们假设一下
thread one将变量a设置为1
thread两个将变量a设置为2
你会得到 race condition ,即使有一个互斥,例如因为
如果先执行第一个线程,则得到a = 1,然后a = 2 .
如果先执行第二个线程,则得到a = 2,然后a = 1 .
线程的顺序取决于操作系统,并且没有关于首先使用哪个线程的保证 . 否则它将是顺序的,不需要在单独的线程中执行它 .
现在假设您根本没有同步,并且您在第一个线程中执行a = a 1,在第二个线程中执行a = a 2 . a的初始值为0 .
在汇编中,生成的代码是将a的值复制到一个寄存器中,向它添加1(在第一个线程的情况下,否则为2) .
如果您有 no synchronization at all ,则可以按照以下顺序进行操作
Thread1:复制到reg1的值 . reg1包含0
Thread2:复制到reg2的值 . reg2包含0
Thread1:添加了reg1的值1.现在包含1
Thread2:添加了reg2的值2.现在包含2
Thread1:添加了reg1的值1.现在包含1
Thread2:添加了reg2的值2.现在包含2
Thread1:reg1的值放到a . 现在一个包含1
Thread2:reg2的值放到a . 现在一个包含2
如果你执行了thread1,那么 sequentially thread 2你最后会有一个= 3 .
现在假设a是一个指针,即一个adressm,因为你知道,得到一个错误的指针地址会导致程序崩溃 . 因此错误的同步可能导致程序 crash .
说得通?