【问题标题】:How to solve race condition?如何解决竞态条件?
【发布时间】:2021-09-30 06:52:11
【问题描述】:

main() 函数中全局变量var 的值有时为-1,有时为1。如何在不使用睡眠功能的情况下编写健壮的代码,以便线程有时间启动和运行。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int var = -1; // GLobal Variable
  
void *myThreadFun(void *vargp)
{
    var = 1;
    return NULL;
}
   
int main()
{
    pthread_t thread_id;
    printf("Before Thread\n");
    pthread_create(&thread_id, NULL, myThreadFun, NULL);

    printf("var=%d",var);

    pthread_join(thread_id, NULL);
    printf("After Thread\n");
    exit(0);
}

【问题讨论】:

  • 仅供参考:POSIX 线程不是 Linux 特定的。
  • sleep() 是计时功能,不是同步功能。它在管理线程间操作中没有任何位置。如果您确实追求稳健性,请使用适当的同步。

标签: c multithreading pthreads race-condition


【解决方案1】:

main() 函数中的全局变量 var 的值有时会出现 -1 和 有时 1 .如何在不使用睡眠功能的情况下编写健壮的代码 以便线程有时间启动和运行。

对于那些刚接触多线程编程的人来说,一个令人痛苦的普遍误解是,像您这样的问题是时间问题。情况并非如此,至少从大多数现代高级编程语言的线程和内存模型的角度来看并非如此。仅靠延迟无法确保一个线程会看到另一个线程对内存产生的影响,因此健壮的代码不会为此目的使用诸如 sleep() 之类的计时函数。

相反,问题是同步之一。该区域包含有关一个线程写入内存的内容必须对其他线程可见的规则。它还涵盖了用于使线程能够影响其他线程的执行的特殊类型的对象和函数,通常是通过暂时阻止它们继续进行。这两个方面密切相关。

pthread_create()pthread_join() 函数具有同步效果。除其他事项外,线程 T1 在调用 pthread_create() 以启动线程 T2 之前对内存的所有写入对 T2 都是可见的(对同一内存进行模后续重写)。在通过pthread_join() 成功加入 T2 后,T2 对内存的所有写入对 T1 可见。因此,对所提出问题的一种解决方案是等到加入第二个线程之后再尝试读取var,正如@Robert's answer 所建议的那样。

如果不希望这样做,那么您将需要使用某种其他类型的同步机制来使主线程等待第二个更新var。这种对同步对象的等待也会使第二个线程的写入对主线程可见。

pthreads 提供的最通用的同步技术是条件变量,它必须与互斥体一起使用。您会在 SO 和其他地方找到许多关于如何正确使用 CV 的解释。

但是,对于您的特定情况,您可能会发现信号量更易于设置和使用。信号量在技术上与 pthreads 库本身是分开的,但它们具有合适的同步语义,既可以使线程等待,也可以使一个线程的内存操作对另一个线程可见。可能看起来像这样:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

int var = -1; // GLobal Variable
sem_t semaphore;
  
void *myThreadFun(void *vargp)
{
    var = 1;

    // increment the semaphore's value
    sem_post(&semaphore);

    return NULL;
}
   
int main()
{
    pthread_t thread_id;

    // initialize the semaphore with value 0
    sem_init(&semaphore, 0, 0);

    printf("Before Thread\n");
    pthread_create(&thread_id, NULL, myThreadFun, NULL);

    // Wait until the semaphore's value can be decremented by one without
    // it becoming negative (and then perform the decrement before proceeding).
    sem_wait(&semaphore);

    printf("var=%d",var);

    pthread_join(thread_id, NULL);
    printf("After Thread\n");
    exit(0);
}

【讨论】:

    【解决方案2】:

    我认为你的代码很健壮,只是你print 的结果不正确。在打印结果之前,你需要确保你的线程已经完成了他的工作。

    如果你print之前调用pthread_join的结果,有两种可能:

    • myThreadFun 已经更改了var,在这种情况下var 将包含值1
    • myThreadFun 尚未完全执行,此时var 的初始值为-1

    如果你print调用pthread_join后的结果,函数myThreadFun将被完全执行并打印1

    int main()
    {
        pthread_t thread_id;
        printf("Before Thread\n");
        pthread_create(&thread_id, NULL, myThreadFun, NULL);
    
        pthread_join(thread_id, NULL);
        printf("After Thread\n");
    
        printf("var=%d",var); /* Here, the thread has completed */
    
        exit(0);
    }
    

    【讨论】:

    • 问题 pthread_create 和 pthread_join 之间有 100 行代码,所以不能像你一样把它移到下面
    • 有哪些工具可以在不添加睡眠的情况下通过线程更新 var 值
    • pthread_join等待线程终止。您也可以使用pthread_cond_wait() 等待所谓的condition variable 更改其值。
    • 简而言之,我的问题是如何等待线程启动
    • 那你可以去ikegami解决方案或寻找pthread_cond_wait
    【解决方案3】:

    Robert 的解决方案是明智的,但我相信您实际上并不想等待线程完成。

    如果您希望主线程等待线程设置变量而不等待线程结束,您将需要某种同步。我将使用信号量,但还有许多其他解决方案。

    #include <pthread.h>
    #include <stdio.h>
    #include <semaphore.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    static sem_t started_threads_sem;
    static int var;
    
    static void *myThreadFun(void *vargp) {
        sleep(3);  // This is the thread doing some initialization.
    
        var = 1;
        sem_post(&started_threads_sem);
    
        sleep(3);  // This is the thread doing stuff.
    
        return NULL;
    }
    
    int main() {
        printf("Before Thread\n");
        sem_init(&started_threads_sem, 0, 0);
    
        pthread_t thread_id;
        pthread_create(&thread_id, NULL, myThreadFun, NULL);
    
        // Wait for the thread to have started.
        sem_wait(&started_threads_sem);
        printf("var=%d\n", var);
    
        pthread_join(thread_id, NULL);
        printf("After Thread\n");
        exit(0);
    }
    
    Before Thread
    <3 s pause>
    var=1
    <3 s pause>
    After Thread
    

    【讨论】:

      猜你喜欢
      • 2013-10-11
      • 1970-01-01
      • 2019-08-20
      • 1970-01-01
      • 2019-09-19
      • 2015-08-13
      • 1970-01-01
      • 1970-01-01
      • 2020-08-08
      相关资源
      最近更新 更多