【pthread系列-8】POSIX线程中的“一次初始化”

来源:互联网 发布:小猫编程软件下载 编辑:程序博客网 时间:2024/05/15 20:56

 

1.核心接口函数

pthread_once_t once_control = PTHREAD_ONCE_INIT;

int pthread_once(pthread_once_t * once_control,void (*init_routin)(void));

2.问题和解决方案

在正式开始进行计算之前,通常需要完成一些初始化工作,而且这些初始化在程序启动之后只允许进行一次。如果有主函数的话,我们一般都是进入主函数之后首先完成各种初始化工作,然后在启动各个线程进行计算。但是,如果你在编写库函数,就没这么幸运了。串行程序中的做法是使用布尔型变量,将这个布尔型变量设置为false。如果初始化完成了,则将之设置为true。下次再调用初始化时,通过检查这个变量来判定是否初始化已完成。多线程程序中,这样用是不合适的。因为可能有多个线程同时发现这个变量为false,以致进行了多次初始化。

为了适应多线程,可以对传统的方式进行改造。使用布尔型变量+互斥量的方式也可以实现单次初始化的功能,因为任意时刻只有一个线程能够对互斥量加锁。首次拿到这个“许可证”的线程通过判断出标志变量为false,从而可以进行初始化工作,直到完成后再对互斥量解锁,当然一般在解锁之前的前一个语句都是将布尔型标志变量设置为true.其他线程再次对互斥量加锁,拿到“许可证”之后,首先也是判断标志变量,如果已经为true,则不需要判断了。

我们知道互斥量的使用效率是相当高的,而且上面的方法也没有问题。那为什么POSIX还要引入“一次初始化”这个机制呢?再仔细分析下上面的改造过程:为了在多线程环境中使用布尔型标志变量,需要使用互斥量;那么这个互斥量本身的初始化应该是静态初始化的,即使用宏进行初始化;如果这个互斥量是动态开辟的,需要调用pthread_muex_init对其进行初始化,而不能使用PHTEAD_MUTEX_INITIALIZER进行初始化了[1:解释为什么,做实验]。好了,问题出现了,pthread_mutex_init只能进行对互斥量初始化一次,所以为了确保只对互斥量初始化一次,我们可以利用“互斥量+布尔型变量的方法”….所以我们陷入了一个递归问题中,为了解决问题1,我们使用方案A,使用方案A的前提之一就是首先解决问题1.为了从这个递归中“解脱”出来,POSIX提供了pthread_once_t这种类型的控制变量和pthread_once这个函数。当我们在一个符号系统中遇到不可调解的悖论时,在一定情况下,我们可以将它放到一个更大的符号系统中解决这个矛盾。

3.在程序中使用

通常来说,分为以下三步:

1)声明一个pthread_once_t的控制变量,同时此控制变量必须使用PTHREAD_ONCE_INIT这个宏进行静态初始化。称之为“控制变量”是因为由这个变量来控制多线程环境中“一次初始化”的进行。

2)声明并实现一个初始化函数init_routin,这个函数的类型为 void (*) (void)

3)使用pthread_once将第一步中的pthread_once_t和第二步中的init_routin关联起来。

4.pthread_once的工作原理

当线程执行开始执行pthread_once函数后,线程会首先检查控制变量:如果控制变量的状态为“未进行初始化”,则开始执行pthread_once中指定的init_routin函数;如果当前控制变量的状态为“初始化已结束”,则直接返回;如果当前控制变量的状态为“正在进行初始化”,则线程将等待直到控制变量的状态变为“初始化已结束”。

这样能保证所有的线程在执行pthread_once之后的代码时,已经完成初始化操作,而且仅完成一次。