在信号量初始化期间解决竞争条件

时间:2012-09-17 11:52:07

标签: c concurrency semaphore

我有一个必须在线程之间共享的数组,受信号量保护。我把初始化代码放在一个可以多次调用的函数中,一个“构造函数”,如下所示:

#include <stdbool.h> //for bool
#include <semaphore.h>

sem_t global_mutex;
char global_array[N]; // Protected with global_mutex

struct my_struct *new_my_struct(){
    static bool is_init = false; // This will be initialized only once, right?
    if (!is_init){                         // 1
        sem_init(&global_mutex, 0, 1);     // 2
        sem_wait(&global_mutex);           // 3
        if (!is_init){                     // 4
           is_init = true;                 // 5
           ... initialize global_array ... // 6
        }
        sem_post(&global_mutex);           // 7
    }

    ... proceed on the create and return a my_struct pointer ...
}

在理想的世界中,线程将从1到7运行,初始化数组并退出关键区域。即使另一个线程在2中停止,4中的测试也将为false,并且数组不会被覆盖。我没有想到如果一个线程卡在1中并重新初始化信号量会发生什么,但是我认为只要{em>第一个线程将is_init设置为true就不会引起太多关注跑!

现在,如果一个线程在4中停止,并且另一个从开始到完成,则初始化并填充global_array时存在竞争条件。当线程在4次运行时停止时,它将重新初始化数组并删除第一个线程存储的状态。

我想知道是否有任何方法可以不受这种竞争条件的影响(可能是巧妙地使用static?)或者我应该将初始化代码与构造函数分开并在主线程中使用它,当没有并发时。

此代码正在使用中,我还没有遇到竞争条件。但是,据我所知,我希望纠正它。

2 个答案:

答案 0 :(得分:2)

如果信号量的实际用途实际上是互斥量,请仅使用pthread_mutex_t。这些可以静态初始化,因此您的问题就会消失。

语法为

pthread_mutex_t global_mutex = PTHREAD_MUTEX_INITIALIZER;

如果您确实需要动态初始化全局对象,请查看pthread_once。这是POSIX为此类任务预见的类型(pthread_once_t)和函数。

答案 1 :(得分:1)

有几种方法可以进行线程安全的延迟初始化,但这不是其中之一。

pthread_once是一种方法,使用实际上是互斥(静态初始化)来同步初始化的全局互斥是另一种方式。实现可能保证static局部变量的线程安全初始化,但不必(至少,它们不在C11之前,我没有检查过)。

但是,为了同步实际初始化,双重检查锁定不能保证在C或Posix 中工作。检查一个线程中的标志是一个数据竞争,该标志是在另一个线程中设置的,在两个线程中没有某种同步。 pthread_once的实现应尽力在已经完成初始化的常见情况下快速执行。 如果您的实现保证了函数范围的静态变量的线程安全初始化,那么这也将做到最好。除非你真的知道自己在做什么(例如,你自己为某个新系统实现pthread_once),否则请使用其中一个优先考虑自己的尝试,以避免在常见情况下出现代价高昂的锁定。