Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

pthreadについて(シグナル・バリア等)

スレッドによるシグナルの利用法、バリア、それ以外の話題等

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

pthreadはPOSIX仕様に基づくOSにおける非同期処理の仕組みです。他の非同期処理と比較すると新しい仕組みですが、その奥は深くさまざまな機能を持っています。スレッドについて最後となる本稿では、スレッドによるシグナルの利用法、バリア、joinの必要性等の話題について述べます。

目次

はじめに

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

これまでの記事

11. シグナル

 スレッドとシグナルはどちらも非同期処理で、基本的には同居させるべきではないと言われています。確かに注意するべきことはありますが、しかし気をつけて使うことでシグナルハンドラを使用する以上の利点を享受できます。

 以前、筆者はsignalについてのレポートを作成していて、そこでシグナルとスレッドについて詳細な記述をしています。よって今回は説明をシンプルにまとめます。

11.1 受信

 スレッドはあらゆる情報をプロセスと共有していますが、数点だけスレッド固有の情報があります。その中の一つに、シグナルマスク (pthread_sigmask(3))があります。

 補足対象シグナルを、スレッド起動前にsigprocmaskでSIG_BLOCKし、スレッドでは同様のシグナルをpthread_sigmaskでSIG_BLOCKしておくことで、プロセスに対して送信されたシグナルをスレッドが補足することが可能です。

signal_recv.c
/* gcc signal_recv.c -o signal_recv -g -W -Wall -lpthread */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
struct {
    int    sigid;
    char * sigmess;
}   siginfo[] = {
{   SIGINT,   "signal_func: 'SIGINT'  Recv.",  },
{   SIGQUIT,  "signal_func: 'SIGQUIT' Recv.",  },
{   SIGTERM,  "signal_func: 'SIGTERM' Recv.",  },
};
#define array( a )  ( sizeof( a )/sizeof( a[0] ))
void * signal_func( void * arg );
void * worker_func( void * arg );
pthread_t       id[2];
int             end_flg = 0;
int main( ) {
    sigset_t    ss;
    size_t      i;

    sigemptyset( &ss );
    for( i = 0; i < array( siginfo ); i ++ ) {
        sigaddset( &ss, siginfo[i].sigid );
    }
    sigprocmask( SIG_BLOCK, &ss, 0 );

    pthread_create( &id[0], 0, &signal_func, 0 );
    pthread_create( &id[1], 0, &worker_func, 0 );
    pthread_join( id[0], 0 );
    pthread_join( id[1], 0 );
    return 0;
}

void * signal_func( void * arg ) {

    ( void )arg;
    sigset_t    ss;
    int         sig;
    size_t      i;
    pthread_detach( pthread_self( ));

    sigemptyset( &ss );
    for( i = 0; i < array( siginfo ); i ++ ) {
        sigaddset( &ss, siginfo[i].sigid );
    }
    pthread_sigmask( SIG_BLOCK, &ss, 0 );

    while( end_flg == 0 ) {
        if( sigwait( &ss, &sig )) {
            printf( "not SIGINT signal!!\n" );
            continue;
        }
        for( i = 0; i < array( siginfo ); i ++ ) {
            if( siginfo[i].sigid != sig ) {
                continue;
            }
            printf( "%s (%2d) \n", siginfo[i].sigmess, sig );
            break;
        }
    }
    return 0;
}

void * worker_func( void * arg ) {

    ( void )arg;
    int i = 0;
    for( i = 0; i < 10; i ++ ) {
        puts( "in worker_func..." );
        sleep( 2 );
    }
    end_flg = 1;
    pthread_kill( id[0], SIGTERM );
    return 0;
}

 実行すると、定期的に"in worker_func..."が出力されます。画面からCtrl+C、Ctrl+\を入力すると、それに対応したシグナルのメッセージが現れます。また、Ctrl+Zで止めて、画面から"kill %"と入力すると、対応したシグナル(SIGTERM)のメッセージが現れます。

 停止させるには、Ctrl+Zで止めてから画面で"kill -9 %"と入力するか、20秒程待ってください。

11.2 送信

 また、pthread_killを使用することで、プロセスおよびスレッドから対象のスレッドに対してシグナルを送信するができます。pthread_kill は kill(2) と似ています。kill(2)同様、pthread_killも第二引数に0を設定することで、送信対象のスレッドの生存情報を得られます。

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

struct {
    int    sigid;
    char * sigmess;
}   siginfo[] = {
{   SIGINT,   "'SIGINT'  Recv.",  },
{   SIGQUIT,  "'SIGQUIT' Recv.",  },
{   SIGTERM,  "'SIGTERM' Recv.",  },
};
#define array( a )  ( sizeof( a )/sizeof( a[0] ))
void * signal_func( void * arg );

int main( ) {

    pthread_t id;
    sigset_t  ss;
    size_t    i;
    int       rtn;

    sigemptyset( &ss );
    for( i = 0; i < array( siginfo ); i ++ ) {
        sigaddset( &ss, siginfo[i].sigid );
    }
    sigprocmask( SIG_BLOCK, &ss, 0 );

    pthread_create( &id, 0, &signal_func, ( void * )&i );
    for( i = 0; i < array( siginfo ); i ++ ) {
        pthread_kill( id, siginfo[i].sigid );
        sleep( 1 );
    }
    rtn = pthread_kill( id, 0 );
    printf( "pthread_kill:[%d][%s]\n", rtn, strerror( rtn ));
    sleep( 1 );

    rtn = pthread_cancel( id );
    printf( "pthread_cancel:[%d][%s]\n", rtn, strerror( rtn ));
    sleep( 1 );

    rtn = pthread_kill( id, SIGINT );
    printf( "pthread_kill:[%d][%s]\n", rtn, strerror( rtn ));
    return 0;
}

void * signal_func( void * arg ) {
    ( void )arg;
    sigset_t    ss;
    int         sig;
    size_t      i;

    pthread_detach( pthread_self( ));
    sigemptyset( &ss );
    for( i = 0; i < array( siginfo ); i ++ ) {
        sigaddset( &ss, siginfo[i].sigid );
    }
    pthread_sigmask( SIG_BLOCK, &ss, 0 );
    while( 1 ) {
        if( sigwait( &ss, &sig )) {
            printf( "not SIGINT signal!!\n" );
            continue;
        }
        for( i = 0; i < array( siginfo ); i ++ ) {
            if( siginfo[i].sigid != sig ) {
                continue;
            }
            printf( "%s (%2d) \n", siginfo[i].sigmess, sig );
            break;
        }
    }
    return 0;
}

 実行すると、プロセスからスレッドに対して一定間隔にシグナル(SIGINT、SIGQUIT、SIGTERM)が飛びます。その後スレッドの生存確認を行い、pthread_cancelでスレッドを殺し、再度スレッドの生存確認を行っています。

11.3 任意のタイミングでのスレッドの中断・続行

 上記から、スレッドは特定のシグナルが配送されるのを待つことができますが、それを利用してスレッドの任意のタイミングでの一時停止(サスペンド)と再開(レジューム)を実装できます。sigwait/sigwaitifoで待ってもよいのですが、sigsuspendの方で実装しています。

 特定のシグナル(サンプルではCtrl+CによるSIGINT)をシグナルハンドラで受信し、ハンドラからスレッドに対してpthread_killでシグナルを配送、スレッドは特定のシグナルを受信したらsigsuspendでサスペンドします。

 もう一度対象シグナルが届いたらシグナルハンドラ経由でスレッドにシグナルを飛ばし、スレッドは自動でサスペンドから解放され、スレッドが再開される、という流れです。

 プログラム(thread_susp.c)は200行ほどあるので、ダウンロードして使用してください。

guest $ ./thread_susp
Thread 3:       15122  16695  18665  15185  d 2:   17938  14153  18015
thread_signal_handler:Path!! suspend start!!   <--- Ctrl+C 押下
thread_signal_handler:Path!! resume start!!    <--- Ctrl+C 押下
Thread 3: d 5:  24347  24817  25273  23228  24758  26006  19189  22684
thread_signal_handler:Path!! suspend start!!   <--- Ctrl+C 押下
thread_signal_handler:Path!! resume start!!    <--- Ctrl+C 押下
Thread 1 End!!  38532  40000  34817  31860  36293  36016  30908  38621
Thread 0 End!!  40000         34973  32474         36338  31006  39304
Thread 4 End!!                              40000  36897  31362  39816
Thread 7 End!!                35161                37083  31385  40000
Thread 6:                     93329  33701         39676  34950
thread_signal_handler:Path!! suspend start!!   <--- Ctrl+C 押下
thread_signal_handler:Path!! resume start!!    <--- Ctrl+C 押下
Thread 5 End!!                37761                40000
Thread 2 End!!                40000  35254                36985
Thread 6 End!!                       39602                40000
Thread 3 End!!                       40000
all thread was done...
guest $

 ただし注意が必要です。どんな時にサスペンドが発生するか分からないので、pthread_cancelの時も述べましたが、例えばmutex_lockされたままでサスペンドされることで他のスレッドの処理が進まないなどの弊害は容易に想像できます。

 各スレッドに各クライアントが接続されている場合など、長時間にわたるサスペンドがそのまま性能劣化に繋がってしまうことも考えられます。使うときは全スレッドを一気にサスペンドし、すぐにレジュームするようにした方が良いと思います。

11.4 所感

 シグナルと言えばハンドラで捕捉するのが通常だと思いますが、ハンドラ内でできることはかなり限られます。一方、スレッドでハンドラを作成すると自由度があがります。個人的には、シグナル受信処理はスレッドで実装した方が柔軟性もあって良いと思います。


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

著者プロフィール

  • 赤松 エイト(エイト)

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

バックナンバー

連載:pthreadについて

もっと読む

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