pthread竞争条件,可疑行为

时间:2012-03-08 06:53:03

标签: c linux posix

我编写了以下代码来演示同一进程的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;

}

`

我运行了这个程序,并且预计在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)

问题是, 当在输出2中发生竞争条件时,线程1打印1并从处理器换出并且线程2进入。它开始工作并且在线程2打印11之后,它被换出,线程1进入。它必须打印12,而是打印2(实际上应该丢失2)。我无法弄清楚如何。请帮助我理解这里发生的事情。

3 个答案:

答案 0 :(得分:10)

你正在思考C语言,但如果你想考虑竞争条件,你必须考虑较低的水平。

在调试器中,您通常在一行代码上设置断点,并且可以通过单步执行程序来查看正在执行的每行代码。但这不是机器的工作方式,机器可以为每行代码执行几条指令,线程可以在任何地方中断。

让我们来看看这一行。

printf(" %d", c);

在机器代码中,它看起来像这样:

load pointer to " %d" string constant
load value of c global
# <- thread might get interrupted here
call printf

所以这种行为并不意外。您必须先加载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;
}

volatile做什么?

问题中的代码可以转换为汇编代码,如下所示:

load the current value of c
add 1 to it
store it in c
call printf

在递增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

这没有用。事实上,volatile几乎从来没有帮助。大多数C程序员不理解volatile,而且编写多线程代码几乎没用。它对于编写信号处理程序,内存映射IO(设备驱动程序/嵌入式编程)非常有用,对于正确使用setjmp / longjmp非常有用。

<强>脚注:

编译器无法在c的调用中缓存printf的值,因为就编译器而言,printf可以更改c({{毕竟,1}}是一个全局变量。有一天,编译器可能会变得更复杂,并且可能知道c不会更改printf,因此程序可能会更加严重。

答案 1 :(得分:1)

我猜测2的值被缓存在寄存器中,因此线程1没有看到由另一个线程最后设置的c的正确当前值。尝试在volatile声明中使用c关键字,这可能会有所不同。有关volatile的一些讨论,请参阅Why is volatile needed in C?

答案 2 :(得分:0)

我认为你完全走错了路。可能大多数情况下,您不是在访问c,而是访问stdout。在现代操作系统上,对stdio函数的访问是互斥的。如果像你的例子那样在流上有几个线程,那么很有可能它们不按规定服务。这是你正在观察的现象。

衡量或计算真实的竞争条件要比你想象的要困难得多。例如,一种方法是跟踪数组中遇到的所有值。

至少你必须在每个输出前面添加类似线程ID的内容,以了解此输出的来源。尝试像

这样的东西
void *fnC(void * arg)
{
    int id = *(int*)arg;
    for(int i=0;i<10;i++)
    {   
        c++;
        printf(" %d:%d", id, c); 
    }   
}

使用数组(例如

)创建带有指向int参数的指针的线程
int ids[] = { 0, 1 };

而不是NULL