CodeZine(コードジン)

特集ページ一覧

pthreadについて(スケジューリング)

スケジューリングの設定とプライオリティ逆転の回避法

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

9. スケジューリング(サンプル)

9.5 サンプル

 上記のサンプルを作成し、動かします。まずはスケジューリング処理が入っていないスレッドで動作を見ます。

 各スレッドはローカルな変数をカウントアップし閾値に達するとその値を表示します。最大値に達するとスレッドを終了し経過時間を表示します。スレッドが早く終わった場合は、すなわち優先的に処理が割り振られた事を意味します。

sched_normal.c
/* gcc sched_normal.c -o sched_normal -W -Wall -g -lpthread */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sched.h>
#include <sys/time.h>
#include <ctype.h>
#include <unistd.h>

pthread_mutex_t m1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t m2 = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  c1 = PTHREAD_COND_INITIALIZER;

#define THREAD_MAX  4
#define LOOP_CNT    40000000
#define TV2SEC(tv) ((double)((tv).tv_sec) + (double)((tv).tv_usec / 1000000.0))
#define SHOW_POLICY(x)  (  (x) == SCHED_FIFO  ? "FIFO"  \
            : ((x) == SCHED_RR    ? "RR"    \
            ((x) == SCHED_OTHER ? "OTHER" : "unknown" )))
void * thread_func( void * arg );

int main( ) {

    struct sched_param  thread_param;
    pthread_t       id[THREAD_MAX];
    pthread_attr_t  thread_attr;
    int             thread_policy = 0;
    double *        result[THREAD_MAX];
    int             i;

    pthread_attr_init( &thread_attr );
    pthread_attr_getschedpolicy( &thread_attr, &thread_policy );
    pthread_attr_getschedparam( &thread_attr, &thread_param );
    fprintf( stdout, "Default policy %s, priority %d\n",
    SHOW_POLICY( thread_policy ), thread_param.sched_priority );

    pthread_mutex_lock( &m2 );
    pthread_mutex_lock( &m1 );
    for( i = 0; i < THREAD_MAX; i ++ ) {
        pthread_create( &id[i], &thread_attr, thread_func, 
                                              ( void * )&i );
        pthread_cond_wait( &c1, &m1 );
    }
    pthread_mutex_unlock( &m1 );
    pthread_mutex_unlock( &m2 );

    for( i = 0; i < THREAD_MAX; i ++ ) {
        pthread_join( id[i], ( void ** )&result[i] );
    }
    for( i = 0; i < THREAD_MAX; i ++ ) {
        fprintf( stdout, " %d policy %s, priority %d timesec:[%f]\n",
            i, SHOW_POLICY( thread_policy ), 
            thread_param.sched_priority, *result[i] );
    }
    return 0;
}

void * thread_func( void * arg ) {

    pthread_mutex_lock( &m1 );
    int             index = *( int * )arg;
    double *        rtn = malloc( sizeof( double ));
    struct timeval  tv1,tv2;
    int             count, i;

    pthread_cond_signal( &c1 );
    pthread_mutex_unlock( &m1 );

    gettimeofday( &tv1, 0 );

    pthread_mutex_lock( &m2 );
    pthread_mutex_unlock( &m2 );

    for( i = 0, count = 0; i < LOOP_CNT; i ++ ) {
        count ++;
        if( count % 1000 == 0 ) {
            printf( " %d %*.*s%4d\r",
                index, index + 1, index + 1, 
                "\t\t\t\t\t\t\t\t", count / 1000 );
        }
    }
    gettimeofday( &tv2, 0 );
    printf( " %d End!!\n", index );
    *rtn = TV2SEC(tv2) - TV2SEC(tv1);
    return ( void * )rtn;
}

 結果を見ると、適当なタイミングで複数のスレッドに処理権限が与えられ実行している様が分かるかと思います。

guest $ ./sched_normal
Default policy OTHER, priority 0
 0 End!!40000   27899   32418   25134
 2 End!!        35898   40000   30794
 1 End!!        40000           36457
 3 End!!                        40000
 0 policy OTHER, priority 0 timesec:[9.093914]
 1 policy OTHER, priority 0 timesec:[11.593880]
 2 policy OTHER, priority 0 timesec:[10.815355]
 3 policy OTHER, priority 0 timesec:[11.873669]
guest $

 以降はスケジューリング設定が行われたスレッドの動きを見ます。前述しましたが、プライオリティおよびポリシーを設定したスレッドを動かすには特権が必要です。

 下記サンプルはポリシーを SCHED_FIFOに設定し、プライオリティをスレッドごとに変えるか等しくするかオプションで決定できるようにしたものです。

sched_fifo.c
/* gcc sched_fifo.c -o sched_fifo -W -Wall -g -lpthread */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sched.h>
#include <sys/time.h>
#include <ctype.h>
#include <unistd.h>

pthread_mutex_t m1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t m2 = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  c1 = PTHREAD_COND_INITIALIZER;

#define THREAD_MAX  4
#define LOOP_CNT    40000000
#define TV2SEC(tv) ((double)((tv).tv_sec)
                     + (double)((tv).tv_usec / 1000000.0))
#define SHOW_POLICY(x)  (  (x) == SCHED_FIFO  ? "FIFO"  \
            ((x) == SCHED_RR    ? "RR"    \
            : ((x) == SCHED_OTHER ? "OTHER" : "unknown" )))
void * thread_func( void * arg );

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

    struct sched_param  thread_param;
    pthread_t           id[THREAD_MAX];
    pthread_attr_t      thread_attr;
    int                 thread_policy = 0;
    double *            result[THREAD_MAX];
    int                 prio_min = 50, prio_max = 50;
    int                 status = 0;
    int                 i;

    if( argc < 2 ) return 1;
    pthread_attr_init( &thread_attr );
    pthread_attr_setinheritsched( &thread_attr, PTHREAD_EXPLICIT_SCHED );
    pthread_attr_setschedpolicy( &thread_attr, SCHED_FIFO );
    if( tolower( *argv[1] ) == 'f' ) {
        prio_min = sched_get_priority_min( SCHED_FIFO );
        prio_max = sched_get_priority_max( SCHED_FIFO );
    }

    pthread_mutex_lock( &m2 );
    pthread_mutex_lock( &m1 );
    for( i = 0; i < THREAD_MAX; i ++ ) {
        thread_param.sched_priority = prio_min + \
            ( i * ( prio_max - prio_min ) / ( THREAD_MAX - 1 ));
        pthread_attr_getschedpolicy( &thread_attr, &thread_policy );
        fprintf( stdout, " %d policy %s, priority [%d - %d] %d\n",
            i, SHOW_POLICY( thread_policy ),
            prio_min, prio_max, thread_param.sched_priority );

        pthread_attr_setschedparam( &thread_attr, &thread_param );
        status = pthread_create( &id[i], &thread_attr, 
                                 thread_func, ( void * )&i );
        if( status ) {
            fprintf( stdout, "pthread_create Error!! [%d][%s]\n",
                status, strerror( status ));
            return 1;
    }
        pthread_cond_wait( &c1, &m1 );
    }
    pthread_mutex_unlock( &m1 );
    pthread_mutex_unlock( &m2 );

    for( i = 0; i < THREAD_MAX; i ++ ) {
        pthread_join( id[i], ( void ** )&result[i] );
    }
    for( i = 0; i < THREAD_MAX; i ++ ) {
        fprintf( stdout, " %d policy %s, priority %d timesec:[%f]\n",
            i, SHOW_POLICY( thread_policy ),
            thread_param.sched_priority, *result[i] );
    }
    return 0;
}

void * thread_func( void * arg ) {

    pthread_mutex_lock( &m1 );
    int             index = *( int * )arg;
    double *        rtn = malloc( sizeof( double ));
    struct timeval  tv1,tv2;
    int             count, i;

    pthread_cond_signal( &c1 );
    pthread_mutex_unlock( &m1 );

    gettimeofday( &tv1, 0 );
    pthread_mutex_lock( &m2 );
    pthread_mutex_unlock( &m2 );
    for( i = 0, count = 0; i < LOOP_CNT; i ++ ) {
        count ++;
        if( count % 1000 == 0 ) {
            printf( " %d %*.*s%4d\r",
                index, index + 1, index + 1,
                "\t\t\t\t\t\t\t\t", count / 1000 );
    }
    }
    gettimeofday( &tv2, 0 );
    printf( " %d End!!\n", index );
    *rtn = TV2SEC(tv2) - TV2SEC(tv1);
    return ( void * )rtn;
}

 プライオリティをスレッドごとに変えると、プライオリティ値が高い順にスレッドが終了しているのが分かると思います。また上位のスレッドが動いている間、他のスレッドはほとんど処理が行われていないようです。

root # ./sched_fifo f
 0 policy FIFO, priority [1 - 99] 1
 1 policy FIFO, priority [1 - 99] 33
 2 policy FIFO, priority [1 - 99] 66
 3 policy FIFO, priority [1 - 99] 99
 3 End!!                        40000
 2 End!!                40000
 1 End!!   1    40000
 0 End!!40000
 0 policy FIFO, priority 99 timesec:[11.525170]
 1 policy FIFO, priority 99 timesec:[8.741931]
 2 policy FIFO, priority 99 timesec:[6.094549]
 3 policy FIFO, priority 99 timesec:[3.226330]
root #

 プライオリティを全スレッド同じ値に設定すると、実行しているスレッドがランダムに選択されているようです。しかし実行しているスレッドが処理を行っている時は他のスレッドはほとんど処理が行われていません。

root # ./sched_fifo s
 0 policy FIFO, priority [50 - 50] 50
 1 policy FIFO, priority [50 - 50] 50
 2 policy FIFO, priority [50 - 50] 50
 3 policy FIFO, priority [50 - 50] 50
 0 End!!40000
 2 End!!                40000
 1 End!!        40000
 3 End!!                        40000
 0 policy FIFO, priority 50 timesec:[2.519115]
 1 policy FIFO, priority 50 timesec:[8.592291]
 2 policy FIFO, priority 50 timesec:[5.667465]
 3 policy FIFO, priority 50 timesec:[12.023481]
root #

 下記サンプルはポリシーをSCHED_RRに設定し、プライオリティをスレッドごとに変えるか等しくするかオプションで決定できるようにしたものです。sched_rr.cはダウンロードして確認して欲しいのですが、sched_fifo.cとの差分は下記の通りです。

diff sched_fifo.c sched_rr.c
1c1
< /* gcc sched_fifo.c -o sched_fifo -W -Wall -g -lpthread */
---
> /* gcc sched_rr.c -o sched_rr -W -Wall -g -lpthread */
37,40c37,40
<     pthread_attr_setschedpolicy( &thread_attr, SCHED_FIFO );
<     if( tolower( *argv[1] ) == 'f' ) {
<       prio_min = sched_get_priority_min( SCHED_FIFO );
<       prio_max = sched_get_priority_max( SCHED_FIFO );
---
>     pthread_attr_setschedpolicy( &thread_attr, SCHED_RR );
>     if( tolower( *argv[1] ) == 'r' ) {
>       prio_min = sched_get_priority_min( SCHED_RR );
>       prio_max = sched_get_priority_max( SCHED_RR );

 プライオリティをスレッドごとに変えると、SCHED_FIFO同様、プライオリティ値が高い順にスレッドが終了しているのが分かると思います。上位のスレッドが動いている間、他のスレッドはほとんど処理が行われていないのも同じです。

root # ./sched_rr r
 0 policy RR, priority [1 - 99] 1
 1 policy RR, priority [1 - 99] 33
 2 policy RR, priority [1 - 99] 66
 3 policy RR, priority [1 - 99] 99
 3 End!!                        40000
 2 End!!                40000
 1 End!!   1    40000
 0 End!!40000
 0 policy RR, priority 99 timesec:[11.372174]
 1 policy RR, priority 99 timesec:[8.655683]
 2 policy RR, priority 99 timesec:[5.982309]
 3 policy RR, priority 99 timesec:[3.176342]
root #

 プライオリティを全スレッド同じ値に設定すると、違いが出てきます。実行しているスレッドがランダムに選択されているのは同じですが、実行しているスレッドが定期的に入れ替わり処理を行っているようです。

 sched_normalと同じような実行結果になっている事から、OSはスケジューリング処理が設定されていない場合、SCHED_RRでプライオリティを同値にしている処理に近い事を行っているのでしょう。

root # ./sched_rr s
 0 policy RR, priority [50 - 50] 50
 1 policy RR, priority [50 - 50] 50
 2 policy RR, priority [50 - 50] 50
 3 policy RR, priority [50 - 50] 50
 0 End!!40000   8931            13076
 2 End!!        26191   40000   34100
 3 End!!        34480           40000
 1 End!!        40000
 0 policy RR, priority 50 timesec:[4.001951]
 1 policy RR, priority 50 timesec:[11.524190]
 2 policy RR, priority 50 timesec:[9.948760]
 3 policy RR, priority 50 timesec:[11.104461]
root #

 上記は特権を持つプロセスが生成するスレッドのみ実行できました。特権を持たないスレッドでは、sched_normalのように一定間隔でスレッドが実行しているようです(実際は実装依存です)。

 しかしスレッドを複数抱えていながらいつも同じスレッドが動いたり、他のスレッドとローテーション形式で動いて欲しい場合もあります。その様な場合、sched_yieldを使用する事でスレッドを順番に動かす事が可能になります。

 しかしながら全スレッドを常に実行状態から実行可能状態に入れ替えるので、その分だけオーバーヘッドが発生し、全体としては処理が遅くなります。

 sched_yield.cはダウンロードして確認して欲しいのですが、sched_normal.cとの差分は下記の通りです。

diff sched_normal.c sched_yield.c
1c1,2
< /* gcc sched_normal.c -o sched_normal -W -Wall -g -lpthread */
---
> /* gcc sched_yield.c -o sched_yield -W -Wall -g -lpthread */
> #define _GNU_SOURCE
78a80
>           sched_yield( );

 プログラムを実行すると、プロセスの終了までが少し長くなるのが分かります。それと、全スレッドがほぼ同時に終了します。

 これはスレッドを常に実行可能状態から実行状態に、その後すぐに実行可能状態に移行するため、処理経過に差が生じず、スレッドの切り替え処理が多発した結果と思われます。

guest $ ./sched_yield
Default policy OTHER, priority 0
 1 End!!39999   40000   39991   39992
 0 End!!40000           39993   39994
 3 End!!                40000   40000
 2 End!!
 0 policy OTHER, priority 0 timesec:[12.009168]
 1 policy OTHER, priority 0 timesec:[12.008320]
 2 policy OTHER, priority 0 timesec:[12.009503]
 3 policy OTHER, priority 0 timesec:[12.009275]
guest $

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

バックナンバー

連載:pthreadについて

もっと読む

著者プロフィール

  • 赤松 エイト(エイト)

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

あなたにオススメ

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