我有一个必须在线程之间共享的数组,受信号量保护。我把初始化代码放在一个可以多次调用的函数中,一个“构造函数”,如下所示:
#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
?)或者我应该将初始化代码与构造函数分开并在主线程中使用它,当没有并发时。
此代码正在使用中,我还没有遇到竞争条件。但是,据我所知,我希望纠正它。
答案 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
),否则请使用其中一个优先考虑自己的尝试,以避免在常见情况下出现代价高昂的锁定。