Pthread 意外输出但结果不错
Pthread unexpected output but good result
我是 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
您的程序存在数据竞争,因为您的两个线程在没有同步的情况下访问同一个共享变量,并且一些访问是写操作。因此,程序的行为是未定义的。您需要通过适当使用互斥锁、信号量或其他有效的同步机制来保护每个线程对共享计数器的访问(读取和写入)。或者,您可以使用原子类型的计数器。
当并发使用2个线程时,一个线程可以暂停,内核随时将上下文切换到另一个线程,我们无法预测2个线程之间代码执行的顺序。
为简单起见,我将向您展示单核 CPU.
可能发生的情况
在 x86 中,计数器通过如下指令增加:
++(*cp);
movl (0x80123468), %ecx
addl , %ecx
movl %ecx, (0x80123468)
&counter = (0x80123468)
假设线程1进入这段代码,计数器要加1。第一条指令是将计数器的当前值(假设它是 200)加载到寄存器 eax 中。因此,线程 1 的 eax=200。然后第二条指令将 1 加到寄存器;因此 eax=201。
现在发生了一些事情,使内核停止了线程 1 的操作(例如中断),然后将上下文切换到线程 2 到 运行,并进入同一段代码。执行第1条指令,加载counter的值并放入自己的eax中(每个线程都有自己的寄存器),Thread2-eax =200,然后执行第2条指令,Thread2-eax = 201。然后执行第3条指令将值 201 存储到 &counter (0x80123468)。
之后发生上下文切换,Thread2暂停,Thread1再次运行s,继续执行第3条指令,将Thread1-eax值=201存入&counter(0x80123468)。
如你所见,当这种情况发生时,counter只增加1,而j和f都增加1(本来我们期望只有其中一个增加1,而不是两者都增加)
如果计数器 = 10000,你的 while 循环就会中断,所以计数器可能是正确的,计数器增加了 10000 次(不总是,我看到一个场景它可以是 10001,但是它似乎很少发生,你可以尝试通过检查自己弄清楚((*cp)< 10000)
的汇编代码)。然而我们不知道j和f增加了多少次?
我是 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
您的程序存在数据竞争,因为您的两个线程在没有同步的情况下访问同一个共享变量,并且一些访问是写操作。因此,程序的行为是未定义的。您需要通过适当使用互斥锁、信号量或其他有效的同步机制来保护每个线程对共享计数器的访问(读取和写入)。或者,您可以使用原子类型的计数器。
当并发使用2个线程时,一个线程可以暂停,内核随时将上下文切换到另一个线程,我们无法预测2个线程之间代码执行的顺序。 为简单起见,我将向您展示单核 CPU.
可能发生的情况在 x86 中,计数器通过如下指令增加: ++(*cp);
movl (0x80123468), %ecx
addl , %ecx
movl %ecx, (0x80123468)
&counter = (0x80123468)
假设线程1进入这段代码,计数器要加1。第一条指令是将计数器的当前值(假设它是 200)加载到寄存器 eax 中。因此,线程 1 的 eax=200。然后第二条指令将 1 加到寄存器;因此 eax=201。
现在发生了一些事情,使内核停止了线程 1 的操作(例如中断),然后将上下文切换到线程 2 到 运行,并进入同一段代码。执行第1条指令,加载counter的值并放入自己的eax中(每个线程都有自己的寄存器),Thread2-eax =200,然后执行第2条指令,Thread2-eax = 201。然后执行第3条指令将值 201 存储到 &counter (0x80123468)。
之后发生上下文切换,Thread2暂停,Thread1再次运行s,继续执行第3条指令,将Thread1-eax值=201存入&counter(0x80123468)。
如你所见,当这种情况发生时,counter只增加1,而j和f都增加1(本来我们期望只有其中一个增加1,而不是两者都增加)
如果计数器 = 10000,你的 while 循环就会中断,所以计数器可能是正确的,计数器增加了 10000 次(不总是,我看到一个场景它可以是 10001,但是它似乎很少发生,你可以尝试通过检查自己弄清楚((*cp)< 10000)
的汇编代码)。然而我们不知道j和f增加了多少次?