2

我写了这段代码

#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);
}

它展示了这个结果。 结果到代码

有时它的 cnt 为 2000,但大多数时候它给出的结果小于 2000。你能解释一下为什么会发生这种情况或这背后的原因是什么?如何修复它。你的回答和理由肯定会大有帮助。

4

4 回答 4

3

unsigned int cnt=0;在线程之间是可共享的,并且操作++不是原子增加cnt的。两个线程可能读取相同的值cnt并增加,并覆盖cnt。您需要应用一些并发控制机制,例如信号量或互斥量。


如果您要使用以下命令反汇编代码(假设代码名称为thread1.c

~$ gcc thread.c -lpthread -S  

输出汇编代码名称为thread1.s.

您会发现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) 移动%eaxcnt后面

并且由于这一行之间的线程上下文切换,相同的值cnt被多个线程读取。因此cnt++不是原子的。

注意:全局变量是线程可共享的,像你声明的cnt局部变量是线程特定的。 icount()


我修改了您的代码并使用信号量进行了并发控制,现在它可以正常工作了。

仅显示修改后的代码

#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
于 2012-12-22T17:35:31.147 回答
1

增量运算符通常由非原子的读-修改-写实现。

跨线程的非原子读取-修改-写入有时可以这样做:

Thread 1:    Thread 2:     count
Read count   ...           1
Add 1        Read count    1
Write count  Add 1         2 
...          Write count   2

导致计数低于预期。

如果您将跨多个线程访问共享资源,则需要使用某种线程感知锁定机制(例如互斥锁)来保护它。

于 2012-12-22T17:38:19.030 回答
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),例如在访问之前锁定它。

于 2012-12-22T17:38:46.580 回答
1

你有一个比赛条件问题。更多信息(我知道它谈到了视觉基础,只是跳过这些东西)在这里
要解决它,您需要一个互斥锁,将其声明为全局变量:

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);
}

所有这一切都是因为有两个线程递增同一个变量,他们在递增变量之前获取变量并将其放入寄存器中。所以他们认为他们正在读取的值不是唯一的:每个线程都有它自己的副本,所以每个线程可能会忽略另一个的变化,直到他们有效地将其写入内存中的地址。
注意:如果您希望线程以有序的方式更新变量(即:线程 1 计数到 NITERS 而不会被中断,当线程 2 开始计数时),您必须在 for 之前锁定互斥锁。

于 2012-12-22T17:45:24.470 回答