转自 http://www.funfunsay.com/?p=263
很多C程序库都实现为“动态初始化”,即初始化状态在库函数的入口处进行判断,在库函数首次被调用的时候隐式完成初始化。动态初始化的方法可以被称为“lazy initialize/Lazy Init”,而与之相对的另一种方法被称为“eager initialize/Eager Init”,一般通过提供一个需要被显式调用的库初始化函数,并且确保该函数被优先于其它库函数调用。Eager Init和Lazy Init都是为了避免重复初始化,实现“单次初始化”或“一次初始化”。如果你不想使用显式的库初始化函数,那就需要实现动态初始化了。
如何实现动态初始化呢?在开发用于单线程的程序库时比较简单,使用一个静态变量用于控制初始化状态即可实现动态初始化。例如:
- static int random_is_initialized = 0;
- extern int initialize_random();
- int random_function()
- {
- if (random_is_initialized == 0) {
- initialize_random();
- random_is_initialized = 1;
- }
- ... /* Operations performed after initialization. */
- }
然而,在提供给多线程程序的C程序库中,情况就没那么简单。例如在上述代码中,必须使用同步机制保护变量random_is_initialized。我们可以使用互斥量来进行保护,然而,应该如何对互斥量进行初始化呢?如何确保互斥量仅被初始化一次呢?显然,我们需要寻得系统底层的支持。
在Linux中,支持动态初始化的API是pthread_once,相关的重要数据类型为pthread_once_t。其代码如下:
- pthread_once_t once_control = PTHREAD_ONCE_INIT;
- int pthread_once(pthread_once_t* once_control, void (*init_routine)(void));
函数pthread_once可以保证多线程的程序中,对函数init_routine仅调用一次。在谷歌公司的开源数据库LevelDB的Linux版本中就使用了动态初始化技术提供一个全局的逐字节比较器对象。参考如下代码:
- typedef pthread_once_t OnceType;
- #define LEVELDB_ONCE_INIT PTHREAD_ONCE_INIT
- static port::OnceType once = LEVELDB_ONCE_INIT;
- static const Comparator* bytewise;
- static void InitModule() {
- bytewise = new BytewiseComparatorImpl;
- }
- const Comparator* BytewiseComparator() {
- port::InitOnce(&once, InitModule);
- return bytewise;
- }
其中”port::InitOnce”的定义为:
- static void PthreadCall(const char* label, int result) {
- if (result != 0) {
- fprintf(stderr, "pthread %s: %sn", label, strerror(result));
- abort();
- }
- }
- void InitOnce(OnceType* once, void (*initializer)()) {
- PthreadCall("once", pthread_once(once, initializer));
- }
这样,拥有多线程的客户程序在各个线程中调用函数BytewiseComparator时,变量bytewise 只被初始构造一次,其余时刻都被直接作为返回值传递。
在Windows Vista及以上版本中,可以使用类型INIT_ONCE和函数InitOnceExecuteOnce来实现与Linux的pthread_once类似的功能。参考MSDN的文章“One-Time Initialization”和示例说明“Using One-Time Initialization”。但是对于Windows Server 2003和Windows XP等系统,我们只能自己通过interlocked functions 或其它的同步机制实现单次初始化。
下面的代码使用interlocked functions将上述LevelDB中的代码移植到Windows上。
- typedef uint32_t OnceType;
- void InitOnce(OnceType* once, void (*initializer)()) {
- while(1){
- LONG prev=InterlockedCompareExchange((uint32_t*)once, 1, LEVELDB_ONCE_INIT);
- if(prev==2){
- return;
- }else if(LEVELDB_ONCE_INIT==prev){
- //go to lock
- break;
- }else{
- // Another thread is initializing.
- // must wait.
- }
- }
- initializer();
- InterlockedExchange((uint32_t*)once, 2);
- }
当一个线程获得初始化机会时,必须使得其它的线程等待初始化完成(上述代码中2==prev的情况),所以必须有一个等待机制,这里使用的是Spin-Lock机制和一个三态控制变量。
可以参考Dr.Bobb上的文章。