10. キャンセル(非同期取り消し)
10.5 非同期取り消し
今まで当章では遅延取り消しについて述べて来ました。基本的に取り消しポイントとなる関数群は遅延取り消しになっていて、特に意識することが無ければ遅延取り消しになってます。
しかし取り消しポイントすら意識せず、強制的に今すぐキャンセルする方法があります。それが非同期取り消しです。
/* gcc cancel_async.c -o cancel_async -W -Wall -g -lpthread */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> static int counter; void * thread_func( void * arg ) ; int main( int argc, char ** argv ) { pthread_t thread_id; void * result = 0; if( argc < 2 ) return 1; pthread_create( &thread_id, 0, thread_func, 0 ); fprintf( stdout, "sleep %dsec.\n", atoi( argv[1] )); sleep( atoi( argv[1] )); pthread_cancel( thread_id ); fprintf( stdout, "Thread cancell call!! %d\n", counter ); pthread_join( thread_id, &result ); if( result == PTHREAD_CANCELED ) fprintf( stdout, "cancelled at iteration %d\n", counter ); else fprintf( stdout, "Thread was not cancelled\n" ); return 0; } void * thread_func( void * arg ) { ( void )arg; int cancel_type = 0; int state = 0; int i = 0; pthread_setcanceltype( PTHREAD_CANCEL_ASYNCHRONOUS, &cancel_type ); pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, &state ); for( counter = 0; ; counter ++ ) { if( counter % 1000 == 0 && counter != 0 ) { if( i == 0 ) { pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, &state ); } sleep( 1 ); if( i == 0 ) { pthread_setcancelstate( state, &state ); } i ^= 1; } } return 0; }
上記サンプルはcancel_disable.c
とほとんど同じです。引数数値が整数の時はPTHREAD_CANCEL_ENABLEが設定されているからか、thread_join
にてキャンセルされたと判定できます。
しかし引数数値が奇数の時は、PTHREAD_CANCEL_DISABLEが設定されているにもかかわらず数値がずれません。すなわち無視されている事になります。さらにその時はpthread_join
の戻り値が0になるようです。おそらくスレッドは正常終了した事になっているのでしょう。
強制的にスレッドを正常終了させているらしいので、pthread_join
としてはキャンセルされたのか正常終了なのか分からないという事になります。
一般的には非同期取り消し処理はほとんど使われていないようです。前述したようにキャンセル処理は、pthread_cancel
を発行する側はスレッドがどのような状態にあるのかを判定できません。つまりスレッドがmutec_lock
をしているのかmalloc
をしているのか、一切わからない状態でcancelを投げるのです。
遅延取り消し処理であれば取り消しポイントに対し、pthread_setcancelstate
でもってキャンセル不可能にさせる事も可能です。また取り消し処理が行われた際にクリーンアップハンドラで後始末してから終わる事もできます。
しかし非同期取り消し処理はそれらが一切できません。
取り消された時と場合によってデッドロックに代表されるスレッドのサスペンド、メモリリークなどが発生しうる事は容易に想像できます。非同期取り消し処理は行わないようにした方がよいです。
10.6 所感
キャンセル処理について見てきましたが、スレッドのキャンセルに「非同期」「遅延」の二種類があることは既に述べた通りです。「非同期キャンセル」が非常に厄介な問題を数々引き起こす元凶であることも既に述べました。遅延キャンセルは非同期キャンセルほどさまざまな問題は引き起こさないのですが、それでも注意事項はたくさんあります。次に示すような注意事項をすべて把握してから使用するようにしましょう。
- キャンセルポイントの把握
- クリーンアップは必ず行う
- C++とクリーンアップの相性問題
pthread_cleanup_push
/pop
関数とC++の例外機構がどう相互作用するかも環境依存のようです。/*
g++ cplus_cancel.cc -o cplus_cancel -W -Wall -g -lpthread
*/ #include <stdio.h> #include <unistd.h> #include <pthread.h> pthread_mutex_t mutex; pthread_cond_t cond; void * thread_func( void * arg ); class test_class { public: test_class( ) { puts( "now Constructor Path!!" ); } ~test_class() { puts( "now Destructor Path!!" ); } }; int main( ) { pthread_t tid; void * rp = 0; pthread_mutex_init( &mutex, 0 ); pthread_cond_init ( &cond, 0 ); pthread_create( &tid, 0, thread_func, 0 ); sleep( 1 ); pthread_cancel( tid ); int rtn = pthread_join( tid, &rp ); fprintf( stdout, "pthread_join:return : [%d]\n", rtn ); return 0; } void * thread_func( void * arg ) { ( void )arg; test_class cls; pthread_cond_wait( &cond, &mutex ); puts( "thread_func:Path!!" ); return 0; }
というわけで、個人的にはキャンセル処理は危険なので使用すべきで無い、という見解を持ってます。上記のようなデッドロックが発生しなさそうな場面であれば差し支えないでしょう。
汎用的に使用するようなプログラムであればクリーンアップ処理を加えておくのは必要と言えますが、アプリケーション作成時にはpthread_cancelは使用不可にした方がよいと思います。