C程序库的Lazy Init 动态初始化

转自 http://www.funfunsay.com/?p=263

很多C程序库都实现为“动态初始化”,即初始化状态在库函数的入口处进行判断,在库函数首次被调用的时候隐式完成初始化。动态初始化的方法可以被称为“lazy initialize/Lazy Init”,而与之相对的另一种方法被称为“eager initialize/Eager Init”,一般通过提供一个需要被显式调用的库初始化函数,并且确保该函数被优先于其它库函数调用。Eager Init和Lazy Init都是为了避免重复初始化,实现“单次初始化”或“一次初始化”。如果你不想使用显式的库初始化函数,那就需要实现动态初始化了。

如何实现动态初始化呢?在开发用于单线程的程序库时比较简单,使用一个静态变量用于控制初始化状态即可实现动态初始化。例如:

 
 
  1. static int random_is_initialized = 0;
  2. extern int initialize_random();
  3. int random_function()
  4. {
  5.     if (random_is_initialized == 0) {
  6.         initialize_random();
  7.         random_is_initialized = 1;
  8.     }
  9.     ... /* Operations performed after initialization. */
  10. }

然而,在提供给多线程程序的C程序库中,情况就没那么简单。例如在上述代码中,必须使用同步机制保护变量random_is_initialized。我们可以使用互斥量来进行保护,然而,应该如何对互斥量进行初始化呢?如何确保互斥量仅被初始化一次呢?显然,我们需要寻得系统底层的支持。

在Linux中,支持动态初始化的API是pthread_once,相关的重要数据类型为pthread_once_t。其代码如下:

 
 
  1. pthread_once_t once_control = PTHREAD_ONCE_INIT;
  2. int pthread_once(pthread_once_t* once_control, void (*init_routine)(void));

函数pthread_once可以保证多线程的程序中,对函数init_routine仅调用一次。在谷歌公司的开源数据库LevelDB的Linux版本中就使用了动态初始化技术提供一个全局的逐字节比较器对象。参考如下代码:

 
 
  1. typedef pthread_once_t OnceType;
  2. #define LEVELDB_ONCE_INIT PTHREAD_ONCE_INIT
  3. static port::OnceType once = LEVELDB_ONCE_INIT;
  4. static const Comparator* bytewise;
  5. static void InitModule() {
  6.     bytewise = new BytewiseComparatorImpl;
  7. }
  8. const Comparator* BytewiseComparator() {
  9.     port::InitOnce(&once, InitModule);
  10.     return bytewise;
  11. }

其中”port::InitOnce”的定义为:

 
 
  1. static void PthreadCall(const char* label, int result) {
  2.     if (result != 0) {
  3.         fprintf(stderr, "pthread %s: %sn", label, strerror(result));
  4.         abort();
  5.     }
  6. }
  7. void InitOnce(OnceType* once, void (*initializer)()) {
  8.     PthreadCall("once", pthread_once(once, initializer));
  9. }

这样,拥有多线程的客户程序在各个线程中调用函数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上。

 
 
  1. typedef uint32_t OnceType;
  2. void InitOnce(OnceType* once, void (*initializer)()) {
  3.     while(1){
  4.         LONG prev=InterlockedCompareExchange((uint32_t*)once, 1, LEVELDB_ONCE_INIT);
  5.         if(prev==2){
  6.             return;
  7.         }else if(LEVELDB_ONCE_INIT==prev){
  8.             //go to lock
  9.             break;
  10.         }else{
  11.             // Another thread is initializing.
  12.             // must wait.
  13.         }
  14.     }
  15.     initializer();
  16.     InterlockedExchange((uint32_t*)once, 2);
  17. }

当一个线程获得初始化机会时,必须使得其它的线程等待初始化完成(上述代码中2==prev的情况),所以必须有一个等待机制,这里使用的是Spin-Lock机制和一个三态控制变量。
可以参考Dr.Bobb上的文章


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值