CodeZine(コードジン)

特集ページ一覧

pthreadについて(キャンセル)

スレッドのキャンセル方法、およびその影響や注意点

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2008/02/07 14:00
目次

10. キャンセル(非同期取り消し)

10.5 非同期取り消し

 今まで当章では遅延取り消しについて述べて来ました。基本的に取り消しポイントとなる関数群は遅延取り消しになっていて、特に意識することが無ければ遅延取り消しになってます。

 しかし取り消しポイントすら意識せず、強制的に今すぐキャンセルする方法があります。それが非同期取り消しです。

cancel_async.c
/* 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 所感

 キャンセル処理について見てきましたが、スレッドのキャンセルに「非同期」「遅延」の二種類があることは既に述べた通りです。「非同期キャンセル」が非常に厄介な問題を数々引き起こす元凶であることも既に述べました。遅延キャンセルは非同期キャンセルほどさまざまな問題は引き起こさないのですが、それでも注意事項はたくさんあります。次に示すような注意事項をすべて把握してから使用するようにしましょう。

  1. キャンセルポイントの把握
  2. 既に提示しましたが、規格にキャンセルポイントの一覧があります。そこには「次の関数はキャンセルポイントとするべきである(shall)」という文章と、「次の関数はキャンセルポイントとしてもよい(may)」という文章があります。
    ここから判断するに、キャンセルポイントとはつまり実装依存という事になります。つまり移植性が怪しいという事です。
    下手をするとOSのバージョンを上げただけで動かなくなるかもしれません。それも、たま~に動かなくなる状況が発生します。
    他の人が作った関数をスレッド上で呼ぶ際に、キャンセルポイントが意識されているか把握することも重要でしょう(把握したところで担保をとれるわけではないのですが)。
  1. クリーンアップは必ず行う
  2. スレッドがどのような状態でキャンセルを受けたのか知る術がないので、そのスレッドがmutex_lockをしているかもしれないと疑うのは当然のことです。かならずunlockしましょう。
     
  1. C++とクリーンアップの相性問題
  2. これは私が体験した事ではないですが、C++で遅延キャンセルされた際、スタック上のオブジェクトのデストラクタが呼ばれるかどうかは環に境依存するらしいです。さらに、pthread_cleanup_push/pop関数とC++の例外機構がどう相互作用するかも環境依存のようです。
    下記サンプルは、私の環境ではデストラクタがちゃんと呼ばれました。
    cplus_cancel.cc
    /*
    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; }
    デストラクタが呼ばれない、例外がthrowされた時にcleanupが行われないというのは、そのままメモリリークやデッドロック、クラッシュなどの原因となり、且つ実装依存なので再現性も不定となり、解決するにはかなり困難だというのが想像できます。C++を使用する場合はスレッドの遅延キャンセルを使用しないのが無難でしょう。

 というわけで、個人的にはキャンセル処理は危険なので使用すべきで無い、という見解を持ってます。上記のようなデッドロックが発生しなさそうな場面であれば差し支えないでしょう。

 汎用的に使用するようなプログラムであればクリーンアップ処理を加えておくのは必要と言えますが、アプリケーション作成時にはpthread_cancelは使用不可にした方がよいと思います。



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

バックナンバー

連載:pthreadについて

もっと読む

著者プロフィール

  • 赤松 エイト(エイト)

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

あなたにオススメ

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