はじめに
この連載ではUNIX系OSなどで使われるスレッド「pthread」についてサンプルを交えて説明していきます。pthreadはPOSIXが仕様化したスレッドモデルです。サンプルはCと一部C++、調査環境はFedora 8(2.6.23.1-49.fc8)、32bit、glibc-4.1-2、gcc-4.1.2-33およびFedora Core 6(2.6.18-1.2798.fc6)、32bit、glibc-2.5-3、gcc-4.1.1-30を使用しています。
これまでの記事
- 第1回:pthreadについて(概要・生成)
- 第2回:pthreadについて(同期)
- 第3回:pthreadについて(条件変数・モデル)
6. pthread_once
一般的に初期化処理はプログラムの初め、クラスで言うならコンストラクタで行います。しかしそれができない場合、スレッド自らグローバルな変数を初期化することが必要なときもあるでしょう。
今まで提供したプログラムは初期化処理をmain
関数およびグローバルで宣言した時点で行ってます。しかしスレッドから呼ばれるライブラリを作成する場合、だれかが作るmain
関数が自分達の使う変数を初期化してくれるとは、到底期待できません。
このような場合は呼ばれたエントリ内で初期化する必要がありますが、その場合、全エントリで初期化処理を書いたり、初期化処理が行われたかいちいち判定するのでは、オーバーヘッドもありますし綺麗ではありません。
上記の問題を解決するには、pthread_once
を使用する事で実現できます。
6.1 関数リファレンス
1回きりの実行にかかわる関数は下記の通りです。コチラにも詳細があります。
- int pthread_once( pthread_once_t * once_control, void (*init_routine)(void));
pthread_once_t
:制御変数。PTHREAD_ONCE_INIT にで初期化されなくてはいけません。init_routine
:1回だけ呼ばれる初期化用関数です。
pthread_once
で登録された関数はプロセスで必ず1回だけ呼ばれる事が保証されています。また、指定された関数の呼び出しが戻ってくるまで、pthread_once
を呼び出している全スレッドは呼び出しが終了するまでブロックされます。関数の中からアクセス可能な変数は、もちろんグローバルな変数のみです。
6.2 プログラム
下記がpthread_once
のサンプルです。なお、サンプルは「once_main.c」をドライバとし、「once_thread.c」内の関数をスレッドに乗せて呼んでいます。「once_main.c」はシンプルなのでファイルをダウンロードして参照してください。
/* gcc -g -W -Wall -c -o once_thread.o once_thread.c
gcc -g -W -Wall -c -o once_main.o once_main.c
gcc once_thread.o once_main.o -o once -g -W -Wall -lpthread */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/time.h> #include <pthread.h> #define TV2SEC(tv) \ ((double)((tv).tv_sec) + (double)((tv).tv_usec / 1000000.0)) static void once_func( ); static void comm_func( char * msg ); void * thread_func1( void * arg ); void * thread_func2( void * arg ); void * thread_func3( void * arg ); void * thread_func4( void * arg ); static pthread_once_t key_once = PTHREAD_ONCE_INIT; static pthread_mutex_t mutex; void * thread_func1( void * arg ) { ( void )arg; comm_func( "thread_func1" ); return 0; } void * thread_func2( void * arg ) { ( void )arg; comm_func( "thread_func2" ); return 0; } void * thread_func3( void * arg ) { ( void )arg; comm_func( "thread_func3" ); return 0; } void * thread_func4( void * arg ) { ( void )arg; puts( "in thread_func4, now start." ); sleep( 1 ); puts( "in thread_func4, now end." ); return 0; } static void comm_func( char * msg ) { struct timeval tv1,tv2; gettimeofday( &tv1, 0 ); fprintf( stdout, "in %s, before once_func.\n", msg ); pthread_once( &key_once, once_func ); fprintf( stdout, "in %s, after once_func.\n", msg ); pthread_mutex_lock( &mutex ); fprintf( stdout, "in %s, mutex_lock OK.\n", msg ); sleep( 1 ); pthread_mutex_unlock( &mutex ); fprintf( stdout, "in %s, mutex_unlock OK.\n", msg ); gettimeofday( &tv2, 0 ); fprintf( stdout, "in %s, timesec:[%f]\n", msg, TV2SEC(tv2) - TV2SEC(tv1)); return; } static void once_func( ) { printf( "initializing function Start!!\n" ); pthread_mutex_init( &mutex, 0 ); sleep( 4 ); printf( "initializing function End...\n" ); }
以下は当方の環境にて、プログラムonce
を動かした結果です。
guest $ gcc -g -W -Wall -c -o once_thread.o once_thread.c guest $ gcc -g -W -Wall -c -o once_main.o once_main.c guest $ gcc once_thread.o once_main.o -o once -g -W -Wall -lpthread guest $ ./once in thread_func1, before once_func. initializing function Start!! in thread_func2, before once_func. in thread_func3, before once_func. in thread_func4, now start. in thread_func4, now end. initializing function End... in thread_func1, after once_func. in thread_func1, mutex_lock OK. in thread_func2, after once_func. in thread_func3, after once_func. in thread_func1, mutex_unlock OK. in thread_func1, timesec:[5.012168] in thread_func2, mutex_lock OK. in thread_func2, mutex_unlock OK. in thread_func2, timesec:[6.017278] in thread_func3, mutex_lock OK. in thread_func3, mutex_unlock OK. in thread_func3, timesec:[7.023068] guest $
プログラムを見て頂くと分かりますが、「once_main.c」がスレッドを生成し、「once_thread.c」がスレッド本体(エントリ)として実装されています。エントリではpthread_once
を呼んでいますが、実際に実行されるのは1回だけ(mutexの初期化)。更にpthread_once
が終了するまで、他のスレッドがサスペンドしています。
スレッドを4個起動してますが、一個のスレッドだけがonce_funcを通っていません。once_funcを呼んでいないスレッドは早々と終了している事も判るかと思います。なお、当サンプルで行っているようなmutexの初期化は、むしろ静的に行うのが普通です。
6.3 所感
本来、pthread_once
はmutexの初期化が出来なかった時に開発された機能だそうです。私の経験では、スレッド固有データを作成する時に使用します(スレッド固有データについては7章にて説明)。
pthread_once
を使用する理由の多くは、スレッド間でのグローバルな変数を使用する場合の初期化処理と思われます。