9. スケジューリング(サンプル)
9.5 サンプル
上記のサンプルを作成し、動かします。まずはスケジューリング処理が入っていないスレッドで動作を見ます。
各スレッドはローカルな変数をカウントアップし閾値に達するとその値を表示します。最大値に達するとスレッドを終了し経過時間を表示します。スレッドが早く終わった場合は、すなわち優先的に処理が割り振られた事を意味します。
/* 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に設定し、プライオリティをスレッドごとに変えるか等しくするかオプションで決定できるようにしたものです。
/* 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 $
