【问题标题】:Pthread unexpected output but good resultPthread 意外输出但结果良好
【发布时间】:2019-12-29 14:39:26
【问题描述】:

我是 c 上 pthreads 的新手,所以我只是想用两个线程做一些基本程序,将整数递增直到等于 10000,然后每个线程写入它增加整数的时间,以及principale 线程写入最终结果,在我的输出中最终结果很好(始终等于 10000)但是每个线程的增量时间是错误的,有人可以向我解释为什么会这样吗?

我的代码:

//Threads in C

/* Includes */
#include <unistd.h>     /* Symbolic Constants */
#include <sys/types.h>  /* Primitive System Data Types */ 
#include <errno.h>      /* Errors */
#include <stdio.h>      /* Input/Output */
#include <stdlib.h>     /* General Utilities */
#include <pthread.h>    /* POSIX Threads */
#include <string.h>     /* String handling */

void *T1 (void * par){
    int * cp = (int*)(par);
    printf("Thread 1 begin, counter equals %d \n",*cp);

    int f=0; 
    while((*cp)< 10000) {++(*cp);
                        f++;}
    printf("Thread 1 finished, i've incremented %d times \n",f);
    pthread_exit(NULL);
}
void *T2 (void * par){
    int * cp = (int*)(par);
    printf("Thread 2 begin, counter equals %d \n",*cp);

    int j=0; 
    while((*cp)< 10000) {++(*cp);
                        j++;}
    printf("Thread 2 finished, i've incremented %d times \n",j);
    pthread_exit(NULL);
}

int main(){
    pthread_t idT1, idT2;
    int counter = 0;
    if (pthread_create(&idT1, NULL, T1, &counter) != 0)
        printf("erreur creation");
    if (pthread_create(&idT2, NULL, T2, &counter) != 0)
        printf("erreur creation");
    pthread_join(idT1, NULL);
    pthread_join(idT2, NULL);
    printf(" Total = %d",counter);
    return 0;
}

示例输出:

Thread 1 begin, counter equals 0 
Thread 2 begin, counter equals 0 
Thread 1 finished, i've incremented 10000 times 
Thread 2 finished, i've incremented 8602 times 
 Total = 10000

【问题讨论】:

    标签: c pthreads thread-synchronization


    【解决方案1】:

    当同时使用2个线程时,一个线程可以暂停,内核随时切换到另一个线程,我们无法预测2个线程之间的代码执行顺序。 为简单起见,我将向您展示单核 CPU 可能发生的场景。

    在 x86 中,计数器会增加一些指令,如下所示: ++(*cp);

    movl    (0x80123468), %ecx
    addl    $1, %ecx
    movl    %ecx, (0x80123468)
    

    &计数器 = (0x80123468)

    想象线程 1 进入这段代码,计数器将加一。第一条指令是将计数器的当前值(假设它是 200)加载到寄存器 eax 中。因此,线程 1 的 eax=200。然后第二条指令将 1 添加到寄存器;因此eax = 201。

    现在发生了一些事情,使内核停止线程 1 操作(例如中断),然后将上下文切换到线程 2 运行,并进入同一段代码。执行第一条指令,将counter的值加载到自己的eax中(每个线程都有自己的寄存器),Thread2-eax =200,然后执行第二条指令,Thread2-eax = 201。然后执行第三条指令到将值 201 存储到 &counter (0x80123468) 中。

    之后发生上下文切换,Thread2暂停,Thread1再次运行,继续执行第三条指令,并将Thread1-eax value = 201存入&counter(0x80123468)。

    如您所见,当这种情况发生时,counter 只增加 1,而 j 和 f 都增加 1(最初,我们希望它们中只有一个增加 1,而不是两者都增加) 如果计数器 = 10000,您的 while 循环会中断,因此计数器可能是正确的,计数器会增加 10000 倍(并非总是如此,我看到一个场景可能是 10001,但似乎很少发生,您可以尝试通过检查自己找出答案((*cp)&lt; 10000) 的汇编代码)。但是我们不知道j和f增加了多少次?

    【讨论】:

      【解决方案2】:

      您的程序存在数据竞争,因为您的两个线程在没有同步的情况下访问同一个共享变量,并且其中一些访问是写入。因此程序的行为是未定义的。您需要通过适当使用互斥锁、信号量或其他有效的同步机制来保护每个线程对共享计数器的访问(读取和写入)。或者,您可以使用原子类型的计数器。

      【讨论】:

      • @AmineÖzil,计数器的最终值符合您的预期,但总体而言,程序的结果正确。毕竟,这就是问题所在。但是,是的,程序行为的不确定性是它试图执行的操作的一个特征,并且表现出 UB 的程序有时会产生符合预期的结果。
      • 顺便说一句,这里缺少原子变量或同步允许 GCC 完全跳过 -O3 上的循环。最终做类似if (ctr &lt; 10000){ f = 10000 - ctr; ctr = 10000;} 的事情。 godbolt.org/z/YD_ydz
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-11-05
      • 1970-01-01
      • 2021-11-22
      • 2012-11-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多