CodeZine(コードジン)

特集ページ一覧

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

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

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

10. キャンセル(遅延取り消し)

10.2 遅延取り消し

 スレッドのキャンセルには下記のとおり二種類あります。

  • 非同期取り消し(PTHREAD_CANCEL_ASYNCHRONOUS):キャンセルは即座に行われる
  • 遅延取り消し(PTHREAD_CANCEL_DEFERRED):キャンセルはスレッドの処理が「取り消しポイント」に達するまで遅延される。

 下記サンプルは遅延取り消し方式によるキャンセル処理です。デフォルトでは遅延取り消し方式が選択されるようです。

cancel.c
/* gcc cancel.c -o cancel -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;
    for( counter = 0; ; counter ++ ) {
        if(( counter % 1000 ) == 0 ) {
            pthread_testcancel( );
        }
    }
    return 0;
}

10.3 キャンセルされたくない時

 スレッドによってはキャンセルされては困る事もあるでしょうし、対処しなくてはいけなません。またキャンセル可能な取り消しポイントはpthread_testcancelだけではありません。規格を参照していただければ一覧があります。

 これら取り消しポイントを持つ関数で実行中にキャンセル処理が行われると、まずい場合があります。

 例えばpthread_mutex_lockをしていて取り消しポイントを持つ関数(例えばread)を行っている場合、lockされたままスレッドがキャンセル処理されてしまうと、他のスレッドはずっとサスペンドしたままという事になります。

 このような場合、ある処理を行っている時はキャンセル禁止にする事で対処します。

cancel_disable.c
/* gcc cancel_disable.c -o cancel_disable -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_DEFERRED, &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;
}

 上記サンプルは先のサンプルとほとんど同じです。違うのはスレッド内部で1000回まわるたびにキャンセル可能/不可能を入れ替えている事くらいです。引数の数値が整数の時は止める時の数字と止まった時の数字は等しいですが、奇数の時は数値がずれるはずです。

 例えば、

  • $ ./cancel_disable 4 <--- 両方とも4000という値。
  • $ ./cancel_disable 5 <--- 5000と6000という値になってて、ずれている。

 このような感じでキャンセル処理を使いますが、呼び出し元はpthread_cancelを投げるだけです。対象スレッドは取り消しポイントに対しキャンセル可能/不可能を設定できるため、キャンセル処理自体を選択することができます。取り消し処理をポーリングすることなく、よってオーバーヘッドがないので容易な実装が可能です。

10.4 クリーンアップ

 前述しましたが、例えばmutex_lockされた状態で取り消しポイントを持つ関数でキャンセル処理をされてしまうと、誰もunlockできず、他の全スレッドが再開できない状態になります。例えばpthread_cond_waitは取り消しポイントですが、この時にキャンセル処理をされるとmutexをlockされた状態で起動します。

 このような場合、クリーンアップ処理でmutex_unlockすることでサスペンド状態を回避することができます。

 クリーンアップ処理は、それ以外にもスレッド間の共通情報を操作する事も可能です。

cancel_cleanup.c
/* gcc cancel_cleanup.c -o cancel_cleanup -W -Wall -g -lpthread */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

#define THREAD_MAX 8

typedef struct control_tag {
    int                 counter;
    int                 busy;
    pthread_mutex_t     mutex;
    pthread_cond_t      cond;
}   control_t;

void * thread_func      ( void * arg );
void   cleanup_handler  ( void * arg );

int main( ) {

    control_t   control;
    pthread_t   id[THREAD_MAX];
    void *      result;
    int         i;

    control.counter = 0;
    control.busy    = 1;
    pthread_mutex_init( &control.mutex, 0 );
    pthread_cond_init ( &control.cond,  0 );
    for( i = 0; i < THREAD_MAX; i ++ ) {
        pthread_create( &id[i], 0, thread_func, ( void * )&control );
    }
    sleep( 2 );

    for( i = 0; i < THREAD_MAX; i ++ ) {
        pthread_cancel( id[i] );
        pthread_join( id[i], &result );
        if( result == PTHREAD_CANCELED )
            fprintf( stdout, "thread %d cancelled.\n", i );
        else
            fprintf( stdout, "thread %d was not cancelled.\n", i );
    }
    return 0;
}

void * thread_func( void * arg ) {

    control_t * control = ( control_t * )arg;
    pthread_cleanup_push( cleanup_handler, arg );
    pthread_mutex_lock( &control -> mutex );
    control -> counter ++;

    while( control -> busy ) {
        fprintf( stdout, "thread waiting... \n" );
        pthread_cond_wait( &control -> cond, &control -> mutex );
    }
    pthread_mutex_unlock( &control -> mutex );
    pthread_cleanup_pop( 1 );
    return 0;
}

void cleanup_handler( void * arg ) {

    control_t * control = ( control_t * )arg;
    control -> counter --;
    fprintf( stdout, "cleanup_handler: counter[%d]\n", 
             control -> counter );
    pthread_mutex_unlock( &control -> mutex );
    return ;
}

 pthread_creanup_pushにてクリーンアップハンドラを登録し、pthread_creanup_popにて取り除きます。環境によってはpthread_creanup_popを省略できるらしいですが、汎用性を失う要因になりますので記述した方がよいです。

 クリーンアップハンドラは引数を1つ取るので、スレッド固有の情報に対して操作することができ、かなり使い道がありそうです。キャンセル処理対策には必須な機能でしょう。


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

バックナンバー

連載:pthreadについて

もっと読む

著者プロフィール

  • 赤松 エイト(エイト)

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

あなたにオススメ

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