【问题标题】:why pthread is behaving like this in c为什么 pthread 在 c 中的行为是这样的
【发布时间】:2021-11-08 07:15:09
【问题描述】:

我的代码应该打印 0 1 2 3 4 但为什么输出是从 1 到 5。每次重新运行代码它都会改变输出但范围保持不变 1-5 我得到的输出: 1 2 3 5 5, 1 2 2 5 5, 5 5 5 5 5 预期的输出是: 0 1 2 3 4 谁能告诉我为什么我没有得到预期的输出

#include <stdio.h>
#include <pthread.h>
void* printInt(void*ptr)
{
    int*iptr=(int*)ptr;
    printf("%d\n",*iptr);
}
int main()
{
    pthread_t tid[5];
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    for(int i=0;i<5;i++)
    {
        pthread_create(&tid[i],&attr,printInt,(void*)&i);
    }
    for(int i=0;i<5;i++)
    {
        pthread_join(tid[i],NULL);
    }

    return 0;
}

【问题讨论】:

  • 您将相同的指针传递给每个进程。每个线程实际运行的时间是不可预测的。因此,每个线程可能会在父循环的不同迭代期间运行,因此会看到不受控制的 i 值。需要将单独的指针传递给每个。
  • 您不能也不应该期望 5 个线程按照您生成它们的顺序运行。它们可以按任何顺序或同时运行,这是预期的行为(这就是它们与函数调用不同的原因)

标签: c multithreading for-loop pointers pthreads


【解决方案1】:

在本次通话中

pthread_create(&tid[i],&attr,printInt,(void*)&i);

您正在传递一个指向同一局部变量 i 的指针,该变量可以在线程启动之前在循环中更改,此外,程序可能具有未定义的行为,因为当线程尝试访问时,指向的变量可能是不活动的.

【讨论】:

    【解决方案2】:

    不幸的是,您的代码有两个竞争条件,其中只有一个可以[轻松]修复。

    1. main 中,通过指针 传递i 允许每个线程函数接收[看似] 随机值。那是因为给每个线程的指针指向 same 变量(即i)。例如,传递给线程 0 的值可能是 1,因为 main 执行其循环的速度比线程 0 的启动速度要快。

    2. 即使我们修复了上面的问题[见下文],只是因为线程是创建的 [通过pthread_create]的顺序:0,1,2,3,4 no 保证它们将以相同的顺序执行/打印。例如,它们可以按以下顺序执行:3,0,2,1,4


    为了解决第一个问题,在main,我们可以通过传递i

    pthread_create(&tid[i],&attr,printInt,(void *) (uintptr_t) i);
    

    而且,printInt 的相应变化:

    int i = (uintptr_t) ptr;
    printf("%d\n",i);
    

    但是,如果线程之间没有某种同步,则无法解决第二个问题,这违背了拥有单独线程的目的。

    有不同的[同样混乱的]方法可以做到这一点。

    例如,main 和线程可以通过一个或两个全局变量进行同步,这些变量通过(例如)stdatomic.h 原语存储/获取,以使main 按顺序“授予”对每个线程的访问权限。而且,线程必须通过另一个全局确认完成。

    或者,可以使用互斥体和信号量。


    这是一种[粗略的]同步方法,使用原子。我们为每个任务创建一个struct 来保存给定线程的数据:

    #include <stdio.h>
    #include <stdatomic.h>
    #include <unistd.h>
    #include <pthread.h>
    
    struct tsk {
        pthread_t tsk_tid;
        int tsk_i;
        int tsk_sync;
    };
    
    void *
    printInt(void *ptr)
    {
        struct tsk *tsk = ptr;
    
        // wait for main to give us the "go"
        while (1) {
            if (atomic_load(&tsk->tsk_sync) == 1)
                break;
            usleep(1);
        }
    
        printf("%d\n", tsk->tsk_i);
        fflush(stdout);
    
        // tell main we're done
        atomic_store(&tsk->tsk_sync,2);
    
        return (void *) 0;
    }
    
    int
    main(void)
    {
        struct tsk tsklist[5] = { 0 };
        struct tsk *tsk;
        pthread_attr_t attr;
    
        pthread_attr_init(&attr);
    
        for (int i = 0; i < 5; i++) {
            tsk = &tsklist[i];
            tsk->tsk_i = i;
            atomic_store(&tsk->tsk_sync,0);
            pthread_create(&tsk->tsk_tid, &attr, printInt, tsk);
        }
    
        // grant each thread its turn, and wait for it to acknowledge
        for (int i = 0; i < 5; i++) {
            tsk = &tsklist[i];
            atomic_store(&tsk->tsk_sync,1);
            while (atomic_load(&tsk->tsk_sync) == 1)
                usleep(1);
        }
    
        for (int i = 0; i < 5; i++) {
            tsk = &tsklist[i];
            pthread_join(tsk->tsk_tid, NULL);
        }
    
        return 0;
    }
    

    [可能]有更好的方法来同步线程。

    在此方法中,主线程授予“访问权限”并等待线程“完成”。


    另一种方法是,一旦主线程授予对第一个线程(例如线程 0)的访问权限,那么线程就可以运行并且 it 可以授予对列表中的下一个线程的访问权限(例如线程 0 授予对线程 1 的访问权限等):

    #include <stdio.h>
    #include <stdatomic.h>
    #include <unistd.h>
    #include <pthread.h>
    
    struct tsk {
        pthread_t tsk_tid;
        int tsk_i;
        int tsk_sync;
    };
    
    int owner = -1;
    
    void *
    printInt(void *ptr)
    {
        struct tsk *tsk = ptr;
    
        // wait for somebody to give us the "go"
        while (1) {
            if (atomic_load(&owner) == tsk->tsk_i)
                break;
            usleep(1);
        }
    
        printf("%d\n", tsk->tsk_i);
        fflush(stdout);
    
        // say we're done and start the next thread
        atomic_store(&owner,tsk->tsk_i + 1);
    
        return (void *) 0;
    }
    
    int
    main(void)
    {
        struct tsk tsklist[5] = { 0 };
        struct tsk *tsk;
        pthread_attr_t attr;
    
        pthread_attr_init(&attr);
    
        for (int i = 0; i < 5; i++) {
            tsk = &tsklist[i];
            tsk->tsk_i = i;
            pthread_create(&tsk->tsk_tid, &attr, printInt, tsk);
        }
    
        // start first thread
        atomic_store(&owner,0);
    
        for (int i = 0; i < 5; i++) {
            tsk = &tsklist[i];
            pthread_join(tsk->tsk_tid, NULL);
        }
    
        return 0;
    }
    

    【讨论】:

      【解决方案3】:

      @Vlad 已经为您说明了问题的原因。一种解决方案是将要传入的值保留在数组中。

      int param[5];
      for (int i = 0; i < 5; ++i)
      {
          param[i] = i;
          pthread_create(&tid[i], &attr, printInt, (void*)&param[i]);
      }
      

      这样在线程开始运行之前值不会改变。

      【讨论】:

        猜你喜欢
        • 2017-10-31
        • 1970-01-01
        • 2019-10-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-08-22
        • 1970-01-01
        • 2015-03-12
        相关资源
        最近更新 更多