我写了这段代码
#include <stdio.h> /* Input/Output */
#include <stdlib.h> /* General Utilities */
#include <pthread.h> /* POSIX Threads */
unsigned int cnt=0; /*Count variable%*/
const int NITERS=1000;
void count()
{
int i=0;
for(i=0; i<NITERS; i++)
{
cnt++;
}
pthread_exit(0);
}
int main()
{
pthread_t tid1,tid2;
/* create threads 1 and 2 */
pthread_create(&tid1,NULL,count,NULL);
pthread_create(&tid2,NULL,count,NULL);
/* Main block now waits for both threads to terminate, before it exits
If main block exits, both threads exit, even if the threads have not
finished their work */
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
if(cnt!=(unsigned)NITERS*2)
{
printf("BOOM! cnt=%d, it should be %d\n",cnt,NITERS*2);
}
else
{
printf("OK! cnt=%d\n",cnt);
}
exit(0);
}
它展示了这个结果。
有一段时间它会变成2000,但大部分时间它的结果都不到2000.你能解释它为什么会发生这种情况或者背后的原因是什么?如何解决它。你的答案和理由肯定会有很大的帮助。
答案 0 :(得分:3)
unsigned int cnt=0;
在线程中是可共享的,并且操作++
不会原子地增加cnt
。两个线程可以读取cnt
的相同值并增加,并覆盖cnt
。您需要应用一些并发控制机制,如信号量或互斥量。
如果您要使用以下命令反编译代码(假设代码名称为thread1.c
)
~$ gcc thread.c -lpthread -S
输出汇编代码名称为thread1.s
。
你的wil find cnt++
在你的代码中是低级别的一条指令:
movl $0, -12(%ebp)
jmp .L2
.L3:
movl cnt, %eax
addl $1, %eax
movl %eax, cnt
addl $1, -12(%ebp)
.L2:
movl NITERS, %eax
(1)cnt
拳头移至%eax
(2)然后在%exc
添加一个
(3)将%eax
移到cnt
后面
由于此行之间的线程上下文切换,多个线程读取cnt
的相同值。因此cnt++
不是原子的。
注意:全局变量是可线程共享的,如cnt
,而您在i
中声明的count()
等局部变量是特定于线程的。
我修改了你的代码并使用信号量强制进行并发控制,现在它可以正常工作。
仅显示修改后的代码
#include <pthread.h> /* POSIX Threads */
#include <semaphore.h>
unsigned int cnt=0; /*Count variable%*/
const int NITERS=1000;
sem_t mysem;
void count()
{
int i=0;
for(i=0; i<NITERS; i++)
{
sem_wait(&mysem);
cnt++;
sem_post(&mysem);
}
pthread_exit(0);
}
int main()
{
if ( sem_init(&mysem,0,1) ) {
perror("init");
}
// rest of your code
}
这样做会很好!一些例子:
nms@NMS:~$ ./thread
OK! cnt=2000
nms@NMS:~$ ./thread
OK! cnt=2000
nms@NMS:~$ ./thread
OK! cnt=2000
答案 1 :(得分:1)
增量运算符通常由read-modify-write实现,这是非原子的。
跨线程的非原子读 - 修改 - 写有时可以这样做:
Thread 1: Thread 2: count
Read count ... 1
Add 1 Read count 1
Write count Add 1 2
... Write count 2
导致计数低于预期。
如果要跨多个线程访问共享资源,则需要使用某种线程感知锁定机制(如互斥锁)来保护它。
答案 2 :(得分:1)
您的2个线程在没有保护的情况下访问共享资源,因此适用竞争条件。增加操作不是原子操作,所以你可以在机器操作方面实际上有这样的东西:
Thread 1 Thread 2
Load value of cnt
Load value of cnt
Increase value of cnt
Write value of cnt Increase value of cnt
Write value of cnt
请注意,虽然两个线程都增加了cnt
,但它实际上只增加了1。
如果您希望结果具有确定性,则需要保护共享资源(cnt
),例如:通过在访问之前锁定它。
答案 3 :(得分:1)
你有竞争条件问题。更多信息(我知道它谈论视觉基础,只是跳过这些事情)here。
要解决它,您需要一个互斥锁,将其声明为全局变量:
pthread_mutex_t mux;
初始化:
pthread_mutex_init(&mux,NULL);
然后用它来读取共享变量:
void count()
{
int i=0;
for(i=0; i<NITERS; i++)
{
pthread_mutex_lock(&mux);
cnt++;
pthread_mutex_unlock(&mux);
}
pthread_exit(0);
}
所有这一切都是因为有两个线程递增相同的变量,它们在递增变量之前获取变量并将它们放入寄存器中。因此,它们认为它们正在读取的不是唯一的:每个线程都有它自己的副本所以每个线程可能会忽略另一个的更改,直到它们有效地将其写入内存中的地址 NB :如果你想要线程以有序的方式更新变量(即:线程1计数到NITERS而不被中断,当线程2开始计数时),你必须在for之前锁定互斥锁