5.1 一次初始化(One-time initialization)
typedef int pthread_once_t:在bits/pthreadtypes.h中定义
#define PTHREAD_ONCE_INIT 0 在pthread.h中定义
pthread_once_t once_control = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *once_control,void(*init_routine)(void));
有些事情,我们需要被做,而且仅仅做一次就够了。当你在程序中需要初始化一个变量时,它常常需要在进入main函数以前就需要被做,在这个时候,调用其它的函数或者变量可能依赖于初始化数据,并且,在一些特殊情况中,创建线程或者创建线程-特殊数据等等,也可能依赖于已经初始化了的mutex。这仅仅是一种特殊情况,但是它存在。
如果你正在写一个库,你通常不用写得太华丽。但是你必须要确定,在你将要用到你需要的初始化数据时,它应该已经被初始化完成了。采用静态地(statically)初始经一个mutex能够帮你很多,但有时候,你会发现,采用"一 次初始化数据"这一特性,用起来更方便。
在传统的顺序设计编程中,一次初始化常常用布尔(bootlean)类型来声明。一个控制变量,比如mutex,condition,它被静态的初始化为0,另一些依靠于这个初始化变量的程序检测这个值,如果值仍为0,它将被初始化为1.以后的检查都将跳过这个初始化过程。
传统的编程中,一次初始化过程存在一些问题。当你使用多线程编程时,它将会很麻烦。如果不只一个线程同时执行这个初始化代码时,两个线程都发现初始化值为0,并且都执行初始化,然而,推测起来看,由于初始化数据的状态是一个被mutex保护起来的共享不变量,因此,它也应该仅仅被执行一次,而不是两次。
你可以用boolean变量和通过静态初始化mutex,来编写自己的一次性初始化代码。这在大多例子中,都比使用pthread_once更加便利,也更高效。这主要的原因是因为pthread_once不允许在程序开始进行静态的初始化mutex,或者condition,你只能够 把初始化代码增加到pthread_once的init_routine函数中来达到只进行一次初始化的目的。这样做,只是使得pthread_once作为一个标准的库函数来使用时更加方便,有效。但是,记住,如果方便你就用它,而并非非用不可。
程序once.c 示范了如何使用pthread_once来进行一次性初始化
#include <pthread.h>
#define err_abort(code,text) do { /
fprintf (stdout, "%s at /"%s/":%d: %s/n", /
text, __FILE__, __LINE__, strerror (code)); /
abort (); /
} while (0)
/*必须先静态初始化once_block为PTHREAD_ONCE_INIT ,也就是0*/
pthread_once_t once_block = PTHREAD_ONCE_INIT;
pthread_mutex_t mutex;
/*
* 一次初始化函数,意思就是无论调用多少次phtead_once,他几乎总是只执行一次
*/
void once_init_routine (void)
{
int status;
printf("initilization/n"); ------------------2
status = pthread_mutex_init (&mutex, NULL);
if (status != 0)
err_abort (status, "Init Mutex");
}
/*
* 线程开始函数,调用pthread_once来执行一次初始化
*/
void *thread_routine (void *arg)
{
int status;
printf("%d/n",once_block);
status = pthread_once (&once_block, once_init_routine);--------1
printf("%d/n",once_block);
if (status != 0)
err_abort (status, "Once init");
status = pthread_mutex_lock (&mutex);
if (status != 0)
err_abort (status, "Lock mutex");
printf ("thread_routine has locked the mutex./n");
status = pthread_mutex_unlock (&mutex);
if (status != 0)
err_abort (status, "Unlock mutex");
return NULL;
}
/*主函数*/
int main (int argc, char *argv[])
{
pthread_t thread_id;
char *input, buffer[64];
int status;
status = pthread_create (&thread_id, NULL, thread_routine, NULL);
if (status != 0)
err_abort (status, "Create thread");
printf("second initilization/n");
printf("%d/n",once_block);
status = pthread_once (&once_block, once_init_routine);------------3
if (status != 0)
err_abort (status, "Once init");
status = pthread_mutex_lock (&mutex);
if (status != 0)
err_abort (status, "Lock mutex");
printf ("Main has locked the mutex./n");
status = pthread_mutex_unlock (&mutex);
if (status != 0)
err_abort (status, "Unlock mutex");
status = pthread_join (thread_id, NULL);
if (status != 0)
err_abort (status, "Join thread");
return 0;
}
程序简单讲解:
注意1、2、3步骤
程序开始先静态初始化once_block为0(PTHRAD_ONCE_INIT),并定义mutex。
然后进入主函数创建线程,执行线程函数thread_routine,thread_routine调用pthread_once来检查
程序是否已经初始化了mutex,(这里的第1步)如果once_block为0,则调用once_init_routine进行初始化mutex.(这里的第2步)
然后处理器执行调度pthread_once,也就是第3步,如果pthread_once发现once_block非0,(我运行时once_block为2),则简单的返回,
并不执行once_init_routine。
归纳起来总结如下:
要初始化的互斥量或者条件变量等,放在pthread_once的第二个参数,即init_routine指向的函数中。
pthread_once的第一个参数once_control应该是全局可见的,并且初始值为0,pthread_once依靠检查该值来决定是否
进行初始化,当once_control被初始化完成之后,值变为非0,并且这个值由phtread_once来进行保护不会被外界改变。
但经过一次初始化之后,不论再调用多少次pthread_once,pthread_once都只是简单的返回,而不再进行初始化。
这样做有两个好处是:
1.在一些复杂的程序中,你可以方便的进行检查你所使用的mutex或者condition等是否已经进行了初始化。而不会增加太多额外的代码
2.避免了你自己申请的一个比如boolean类型的变量来检查是否已经进行了初始化时,有可能在外部文件中,被恶意破坏,而得到不正确的值
但不好的地方也是存在的:
它不允许在程序的开始进行静态的初始化,而是额外增加一个初始化函数(这样做是为了做为一个标准的库函数而被调用)
如果你在一些简单的程序中使用时,你完全可以自己静态的初始化一个mutex,然后在某处增加一个逻辑判断,而不是非得用phtread_once不可。
具体视个人情况而定。