Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

pthreadについて(スレッド固有データ)

スレッド単体で管理できる情報の生成・管理方法

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2007/12/28 14:00

pthreadはPOSIX仕様に基づくOSにおける非同期処理の仕組みです。他の非同期処理と比較すると新しい仕組みですが、その奥は深くさまざまな機能を持っています。本稿ではスレッド独自のデータ作成・管理方法を解説します。

目次

はじめに

 この連載では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を使用しています。

これまでの記事

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」はシンプルなのでファイルをダウンロードして参照してください。

once_thread.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を使用する理由の多くは、スレッド間でのグローバルな変数を使用する場合の初期化処理と思われます。


  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • 赤松 エイト(エイト)

    (株)DTSに勤てます。 WebアプリやJavaやLL等の上位アプリ環境を密かに憧れつつも、ず~っとLinuxとかHP-UXばかり、ここ数年はカーネル以上アプリ未満のあたりを行ったり来たりしています。 mixiもやってまして、こちらは子育てとか日々の日記メインです。

バックナンバー

連載:pthreadについて

もっと読む

All contents copyright © 2005-2020 Shoeisha Co., Ltd. All rights reserved. ver.1.5