Shoeisha Technology Media

CodeZine(コードジン)

記事種別から探す

pthreadについて(同期)

pthreadの同期方法

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

pthreadはPOSIX仕様に基づくOSにおける非同期処理の仕組みです。他の非同期処理と比較すると新しい仕組みですが、その奥は深くさまざまな機能を持っています。本稿ではpthreadの同期方法を解説します。

目次

はじめに

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

前回の記事

3.同期(1/4):mutex

 マルチスレッドに限らず、独立した処理を円滑に行うためには共有情報を意識し保護する必要があります。スレッドは非同期で動作しますが、スレッド内の全処理を非同期で処理する事は少なく、通常は仕様上および効率性の理由から、多少なりともスレッドが参照する一部の情報を他スレッドや呼び出し元と同期(他スレッドの排除)する必要があります。

 スレッドはプロセス内の変数を共有しているので、何も対策をとらないと、あるスレッドが書き換えている最中に他のスレッドが更新したり消した時に問題が発生しそうなことは容易に想像がつくでしょう。スレッド間で協調して管理しなくてはいけない情報にアクセスするための機能を、同期処理と言います。そしてPOSIXの仕様では、スレッドの同期処理が3個あります。

  1. mutex
  2. オーソドックスな同期処理。サスペンドで待つ。1番広く使われていて単純で性能も悪くない。
  3. spin
  4. 見た目はmutexと同じだが、ビジーウェイトで待つ。スレッド数で性能値が変わる。
  5. read/write lock
  6. キャッシュ情報へのアクセス時に使われる事が多い、やや高度な同期処理。仕様としては新しく、性能は1番悪い。

 以降、上記3個の同期処理について掘り下げて行きます。

 ちなみに同時に複数のスレッドが共有情報を同時に操作できないようにする事を相互排除(mutual exclusion)と言います。更に相互排除が必要な領域の事をクリティカルセクション(critical section)と言います。

3.1 mutex

 前述したようにmutexは同期処理の1つで、オーソドックスで使いやすい同期処理です。主なmutex関数は次のとおりで、詳細は適時manやココなどで参照してください。

  • int pthread_mutex_init( pthread_mutex_t * mutex, const pthread_mutex_attr_t * mutexattr );
  • int pthread_mutex_destroy( pthread_mutex_t * mutex );
  • int pthread_mutex_lock( pthread_mutex_t * mutex );
  • int pthread_mutex_unlock( pthread_mutex_t * mutex );
  • int pthread_mutex_trylock( pthread_mutex_t * mutex );

 簡単に説明すると、同期用の変数mutexを初期化し、共有情報へアクセスする直前でmutex_lockし、アクセスを終えたらmutex_unlockを行います。trylockはロックを試してみて、ロック出来なかったときはサスペンドせずにエラーコード「EBUSY」が返ります。mutexの使用を終えたらdestroyする、という感じです。プログラムがそのまま終了するならdestroyの省略もありでしょう。

 pthread_mutexattr_tは、プライオリティ逆転の回避およびmutexのタイプを変更するために使用します。プライオリティ逆転とmutexのタイプについてはそれぞれ別章で説明します。当レポートではそれ以外の章ではNULL(初期設定値)を設定しています。

 また、mutexは下記のように初期化することも可能で、どちらも同じ意味です。

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_init( &mutex, 0 );

3.2 サンプル

 以上の関数を使ったサンプルが下記です。前回の1.1章で使用した「alarm_thread.c」の修正です。登録されたアラーム情報を構造体に格納してますが、同時にスレッドからは用済みになった時点でアラーム情報構造体から切り離しています。その情報の操作をmutexで守っています。

alarm_thread_mutex.c
/* gcc alarm_thread_mutex.c -o alarm_thread_mutex -W -Wall -lpthread */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#define BUF_LEN 256

void * alarm_func( void * arg );

typedef struct alarm_str {
    int                 sec;
    char                msg[BUF_LEN];
    FILE *              fp;
    pthread_t           pt;
    struct alarm_str *  prev;
    struct alarm_str *  next;
}   alarm_t;
alarm_t *       head = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

int main( int argc, char ** argv ) {

    alarm_t * alarm;
    FILE *    fp;
    char      line[BUF_LEN];

    if( argc < 2 ) return( 1 );
    fp = fopen( argv[1], "w" );

    while( 1 ) {
        printf( "Alarm ( sec msg ) --> " );
        fgets( line, sizeof( line ), stdin );
        if( strlen( line ) <= 1 ) {
            continue;
        }
        if( memcmp( line, "quit", 4 ) == 0 ) break;
        alarm = malloc( sizeof( alarm_t ));
        alarm -> fp = fp;
        if( sscanf( line, "%d %s", &alarm -> sec, alarm -> msg ) < 2 ) {
            continue;
        }
        pthread_mutex_lock( &mutex );
        alarm -> next = head;
        alarm -> prev = 0;
        if( head ) {
            head -> prev = alarm;
        }
        head = alarm;
        pthread_mutex_unlock( &mutex );
        pthread_create( &alarm -> pt, NULL, &alarm_func, alarm );
        pthread_detach( alarm -> pt );
    }
    pthread_mutex_destroy( &mutex );
    fclose( fp );
    return 0;
}

void * alarm_func( void * arg ) {
    alarm_t * alarm = ( alarm_t * )arg;
    alarm_t * current = 0;
    int       cnt;

    for( cnt = 0; cnt < alarm -> sec; cnt ++ ) {
        flockfile( alarm -> fp );
        fprintf( alarm -> fp, "[%d] (%d) %s\n", 
                 alarm -> sec, cnt, alarm -> msg );
        fflush( alarm -> fp );
        funlockfile( alarm -> fp );
        sleep( 1 );
    }

    pthread_mutex_lock( &mutex );
    for( current = head; current; current = current -> next ) {
        if( current -> pt != pthread_self( )) {
            continue;
        }
        if( current -> next != 0 ) {
            current -> next -> prev = current -> prev;
        }
        if( current -> prev != 0 ) {
            current -> prev -> next = current -> next;
        }
        if( head == current ) {
            head = current -> next;
        }
        break;
    }
    free( current );
    pthread_mutex_unlock( &mutex );
    return 0;
}

3.3 注意点

 これは全同期処理で言えることですが、前述したようにmutexがロックされている間、そのmutexをロックしたいスレッドはサスペンドします。ロックとアンロックの処理順序等によってはデッドロックが発生する事を常に考慮し、回避しなくてはいけません。

 また、ロックしてからの処理に多くの時間が必要である場合、その間他のスレッドがサスペンドしているわけですから、トータルの処理時間は長くなってしまいます。同期するべき箇所を見極め、mutexによるロック対象はなるべく狭めるべきです。


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

著者プロフィール

  • 赤松 エイト(エイト)

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

バックナンバー

連載:pthreadについて

もっと読む

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