我编写了以下代码来演示同一进程的2个线程之间的竞争条件.
`
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int c = 0;
void *fnC()
{
int i;
for(i=0;i<10;i++)
{
c++;
printf(" %d", c);
}
}
int main()
{
int rt1, rt2;
pthread_t t1, t2;
/* Create two threads */
if( (rt1=pthread_create( &t1, NULL, &fnC, NULL)) )
printf("Thread creation failed: %d\n", rt1);
if( (rt2=pthread_create( &t2, NULL, &fnC, NULL)) )
printf("Thread creation failed: %d\n", rt2);
/* Wait for both threads to finish */
pthread_join( t1, NULL);
pthread_join( t2, NULL);
printf ("\n");
return 0;
}
Run Code Online (Sandbox Code Playgroud)
`
我运行了这个程序,并期望在2个线程之间发生竞争条件(但是,据我所知,竞争条件的可能性非常小,因为线程主函数非常小).我跑了50000次.以下是输出,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 - 49657 times (no race condition)
1 3 4 5 6 7 8 9 10 11 2 12 13 14 15 16 17 18 19 20 - 244 times (race condition occurs)
2 3 4 5 6 7 8 9 10 11 1 12 13 14 15 16 17 18 19 20 - 99 times (race condition occurs)
Run Code Online (Sandbox Code Playgroud)
问题是,当输出2中出现竞争条件时,线程1打印1并被交换出处理器并且线程2进入.它开始工作,并且在线程2打印11之后,它被换出,线程1进入.它必须打印12,而是打印2(实际上应该丢失2).我无法弄清楚如何.请帮我理解这里发生的事情.
Die*_*Epp 10
你在思考C语言,但如果你想考虑竞争条件,你必须考虑较低的水平.
在调试器中,通常在单行代码上设置断点,并且可以通过单步执行程序来查看正在执行的每行代码.但这不是机器的工作原理,机器可以为每行代码执行几条指令,线程可以在任何地方中断.
我们来看看这一行.
printf(" %d", c);
Run Code Online (Sandbox Code Playgroud)
在机器代码中,它看起来像这样:
load pointer to " %d" string constant
load value of c global
# <- thread might get interrupted here
call printf
Run Code Online (Sandbox Code Playgroud)
所以这种行为并不出乎意料.您必须c在调用之前加载值printf,因此如果线程被中断,则总是有可能在任何c时候失效printf.除非你做一些事情来阻止它.
修复竞争条件:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int c = 0;
void *func(void *param)
{
int i;
for (i=0; i<10; i++) {
pthread_mutex_lock(&mutex);
c++;
printf(" %d", c);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
Run Code Online (Sandbox Code Playgroud)
怎么volatile办?
问题中的代码可以转换为汇编代码,如下所示:
load the current value of c
add 1 to it
store it in c
call printf
Run Code Online (Sandbox Code Playgroud)
它不需要c在递增后重新加载,因为允许C编译器假设没有其他人(没有其他线程或设备)更改除当前线程之外的内存.
如果您使用volatile,编译器将严格保持每个加载和存储操作,并且程序集将如下所示:
load the current value of c
add 1 to it
store it in c
# compiler is not allowed to cache c
load the current value of c
call printf
Run Code Online (Sandbox Code Playgroud)
这没有用.事实上,volatile几乎从来没有帮助.大多数C程序员都不理解volatile,而且编写多线程代码几乎没用.它对于编写信号处理程序,内存映射IO(设备驱动程序/嵌入式编程)非常有用,并且对于正确使用setjmp/ 非常有用longjmp.
脚注:
编译器无法缓存c调用的值printf,因为只要编译器知道,printf就可以更改c(c毕竟是一个全局变量).有一天,编译器可能会变得更复杂,并且可能知道printf它不会改变c,因此程序可能会更加严重.