7. デストラクタ
7.3 デストラクタ
スレッドでは使用しなくなったスレッド固有データキーはpthread_key_delete
によって破棄できるようになっています。スレッド固有データキーを作製できる数は環境によって異なりますが、128~1024あたりらしいです(PTHREAD_KEYS_MAXで定義されてます)。
よって必要の無くなったキーを破棄するのは必要かもしれません。もちろん破棄するタイミングは慎重に行わなくてはいけません。
破棄する方法ですが、一般的には pthread_key_create の第2引数で指定した、デストラクタ関数を通して行います。デストラクタ関数はスレッドが終了する際に呼ばれ、そのスレッドが使用していたスレッド固有データを引数にしています。
スレッド固有データ内にヒープメモリを格納している場合は、メモリを解放したりなどの処理を行うのが一般的です。スレッド固有データキーを削除するためには、スレッド固有データを使用しているスレッド数も同時に管理する事で対処する必要があります。
ただしデストラクタ関数が呼ばれる順番はスレッド生成順等に依存してないので、その辺りは気をつけて処理しましょう。なお、pthread_join
は、デストラクタ関数が終了した後で呼ばれます。
スレッド固有データの開放などを行うため、スレッド本体と切り離して処理できるので、スレッド本体のプログラム構成がすっきり書くことができて、個人的には気に入っている機能です。下記サンプルは、ちょっと長いですが、グローバルでスレッド数を管理し、その値が0になったらpthread_key_delete
を呼んでます。
また、pthread_once
を使用し、その中でpthread_key_create
を呼んでいます。これはpthread_once
とpthread_key_create
の代表的な使用方法です。
guest $ ./tsd_destructor initializing key in thread, id:b7f52b90 string:"Argu 0" count:0 msg:"in thread_func my_count:0" in thread, id:b7551b90 string:"Argu 1" count:1 msg:"in thread_func my_count:1" in thread, id:b6b50b90 string:"Argu 2" count:2 msg:"in thread_func my_count:2" in thread, id:b614fb90 string:"Argu 3" count:3 msg:"in thread_func my_count:3" in thread, id:b574eb90 string:"Argu 4" count:4 msg:"in thread_func my_count:4" in thread, id:b4d4db90 string:"Argu 5" count:5 msg:"in thread_func my_count:5" in thread, id:b434cb90 string:"Argu 6" count:6 msg:"in thread_func my_count:6" in thread, id:b394bb90 string:"Argu 7" count:7 msg:"in thread_func my_count:7" in destructor, id:b7f52b90 string:"Argu 0" count:0 msg:"in thread_func my_count:0" in destructor, id:b7551b90 string:"Argu 1" count:1 msg:"in thread_func my_count:1" pthread_join id:b7f52b90 msg:"Argu 0" pthread_join id:b7551b90 msg:"Argu 1" in destructor, id:b6b50b90 string:"Argu 2" count:2 msg:"in thread_func my_count:2" in destructor, id:b614fb90 string:"Argu 3" count:3 msg:"in thread_func my_count:3" in destructor, id:b574eb90 string:"Argu 4" count:4 msg:"in thread_func my_count:4" in destructor, id:b4d4db90 string:"Argu 5" count:5 msg:"in thread_func my_count:5" in destructor, id:b434cb90 string:"Argu 6" count:6 msg:"in thread_func my_count:6" in destructor, id:b394bb90 string:"Argu 7" count:7 msg:"in thread_func my_count:7" key deleted ... pthread_join id:b6b50b90 msg:"Argu 2" pthread_join id:b614fb90 msg:"Argu 3" pthread_join id:b574eb90 msg:"Argu 4" pthread_join id:b4d4db90 msg:"Argu 5" pthread_join id:b434cb90 msg:"Argu 6" pthread_join id:b394bb90 msg:"Argu 7" guest $
上記では、スレッド内で設定した情報がスレッド間で固有であり、デストラクタはその情報を引き継ぐ事が出来、且つ全スレッドが終了した時にpthread_key_delete
が実行されている事が分かります。また、pthread_join
も、デストラクタが終わってから呼ばれていて、正しく引数も受け取れている事が分かります。
7. スレッドローカルストレージ(Thread Local Storage:TLS)
7.4 スレッドローカルストレージ(Thread Local Storage:TLS)
実はpthread_getspecific
関数を使うのと同様のことを、特殊なキーワード__thread
を使って実現できます。しかし条件があって、gccのversionが 3.3以降のgccが必要です。
guest $ gcc --version gcc (GCC) 4.1.1 20061011 (Red Hat 4.1.1-30) Copyright (C) 2006 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. guest $
先ほどのサンプル(tsd_once.c と、tsd_once_3rd.c)の違いは、__thread
を付加しているという点だけです。TLSはTSDとほとんど同じ動きをしますから、こちらの方が可読性としては優れています。キーの初期化や解放などの手間も無いので、かなり有用かと思います。
guest $ ./tsd_tls in thread, id:b7f7eb90 string:"Argu 0" count:0 msg:"in thread_func my_count:0" in thread, id:b757db90 string:"Argu 1" count:1 msg:"in thread_func my_count:1" in thread, id:b6b7cb90 string:"Argu 2" count:2 msg:"in thread_func my_count:2" in thread, id:b617bb90 string:"Argu 3" count:3 msg:"in thread_func my_count:3" in thread, id:b577ab90 string:"Argu 4" count:4 msg:"in thread_func my_count:4" in thread, id:b4d79b90 string:"Argu 5" count:5 msg:"in thread_func my_count:5" in thread, id:b4378b90 string:"Argu 6" count:6 msg:"in thread_func my_count:6" in thread, id:b3977b90 string:"Argu 7" count:7 msg:"in thread_func my_count:7" pthread_join id:b7f7eb90 msg:"Argu 0" pthread_join id:b757db90 msg:"Argu 1" pthread_join id:b6b7cb90 msg:"Argu 2" pthread_join id:b617bb90 msg:"Argu 3" pthread_join id:b577ab90 msg:"Argu 4" pthread_join id:b4d79b90 msg:"Argu 5" pthread_join id:b4378b90 msg:"Argu 6" pthread_join id:b3977b90 msg:"Argu 7" guest $
上記はtsd_destructor.cと同じ動きをしていますが、プログラムボリュームもプログラムの分かり易さもtsd_tls.cが優れています。移植性に不安もありますが、スレッドローカルストレージが使える環境であれば、ぜひ使用していくべきでしょう。
7.5 所感
スレッド固有データは便利な機能だと思います。
マルチプロセスのプログラムをマルチスレッドに変更する際、extern変数や static 変数をスレッド固有にするなどの利用価値があります。スレッドをフレームワークっぽく使う場合、上位層に対しグローバルっぽく使える変数として定義し公開したり、デバック情報として格納させてエラー発生時にログ出力する際の領域として使用したり等。
いろいろと利用価値があると思うので、本格的なアプリケーションを作成する場合には使用を検討した方がよいと思います。
次回はスレッド属性におけるスタックサイズとその影響について述べます。