3.同期(2/4):spin
3.4 spin
mutexの場合、ロック待ちしている時はそのスレッドはサスペンド状態です。サスペンド状態とは、自身の処理を保留し、他のスレッドやプロセスに資源を渡して処理させる事を意味します。
spinの場合、ロック待ちしている時はそのスレッドはビジーウェイト状態です。ビジーウェイト状態は、処理を保留せずに、ロック用変数が0(解放)になるまでループして待っている状態です。つまりフルパワーで待っている状態なので、例えばCPU使用率も100%かそれに近い数値になっています。
主なspin関数は以下の通りです。詳細は適時manなどで参照してください。
- int pthread_spin_init( pthread_spinlock_t * __lock, int __pshared )
- int pthread_spin_destroy( pthread_spinlock_t * __lock )
- int pthread_spin_lock( pthread_spinlock_t * __lock )
- int pthread_spin_unlock( pthread_spinlock_t * __lock )
- int pthread_spin_trylock( pthread_spinlock_t * __lock )
int __psharedにはプロセスの共有を行うか行わないかを規定する定数マクロを設定します。種類は下記の通り。
- PTHREAD_PROCESS_PRIVATE:自プロセス内のスレッドにのみ使用。他スレッドと共に使用した時の動きは保証できず。
- PTHREAD_PROCESS_SHARED:他プロセスのスレッドと共有して使用可能。別途プロセス間通信をベースに実装。
下記サンプルプログラムを動かし、スレッドのサスペンド及びビジーウェイト状態でのCPU使用率を見ます。引数が数値が1つ必要で、その数値の秒数分スレッドがサスペンドおよびビジーウェイト状態になります。
/* gcc spin_mutex.c -o spin_mutex -W -Wall -lpthread */ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <sys/time.h> #include <sys/times.h> #include <unistd.h> #define TV2SEC(tv) \ ((double)((tv).tv_sec) + (double)((tv).tv_usec / 1000000.0)) #define LOOP_CNT 10000000 pthread_spinlock_t lock; pthread_mutex_t mutex; void * cpu_info ( void * arg ); void * spin_func ( void * arg ); void * mutex_func( void * arg ); int main( int argc, char ** argv ) { pthread_t id; if( argc < 2 ) return 0; int sleep_cnt = atoi( argv[1] ); pthread_create( &id, 0, cpu_info, ( void * )&sleep_cnt ); pthread_detach( id ); sleep( 4 ); pthread_spin_init( &lock, PTHREAD_PROCESS_PRIVATE ); pthread_spin_lock( &lock ); pthread_create( &id, 0, spin_func, 0 ); sleep( sleep_cnt ); pthread_spin_unlock( &lock ); pthread_join( id, 0 ); pthread_mutex_init( &mutex, 0 ); pthread_mutex_lock( &mutex ); pthread_create( &id, 0, mutex_func, 0 ); sleep( sleep_cnt ); pthread_mutex_unlock( &mutex ); pthread_join( id, 0 ); return 0; } void * cpu_info( void * arg ) { int limit = ( *( int * )arg ) * 2 + 4; char buf[256]; int usr, nice, sys, i; int old_use; clock_t old_time; clock_t now; struct tms wtms; FILE * fp; for( i = 0; i < limit; i ++ ) { fp = fopen( "/proc/stat", "r" ); fscanf( fp, "%s %d %d %d", buf, &usr, &nice, &sys ); fclose( fp ); now = times( &wtms ); printf( "Cpu(s) : %-3f\n", (( double )( usr + nice + sys - old_use ) / ( now - old_time )) * 100 ); old_use = usr + nice + sys; old_time = now; sleep( 1 ); } return 0; } void * spin_func( void * arg ) { ( void )arg; struct timeval tv1, tv2; puts( "spin_func start!!" ); gettimeofday( &tv1, 0 ); pthread_spin_lock( &lock ); gettimeofday( &tv2, 0 ); pthread_spin_unlock( &lock ); printf( "spin_func end ... timesec:[%f]\n", TV2SEC(tv2) - TV2SEC(tv1)); return 0; } void * mutex_func( void * arg ) { ( void )arg; struct timeval tv1, tv2; puts( "mutex_func start!!" ); gettimeofday( &tv1, 0 ); pthread_mutex_lock( &mutex ); gettimeofday( &tv2, 0 ); pthread_mutex_unlock( &mutex ); printf( "mutex_func end ... timesec:[%f]\n", TV2SEC(tv2) - TV2SEC(tv1)); return 0; }
私の環境で試したところ、下記の通りになりました。spin_funcが実行中ではCPU使用率が100%、mutex_funcが実行している時はCPU使用率が0%に落ちました。
gurest $ ./spin_mutex 5 Cpu(s) : 0.000930 Cpu(s) : 0.000000 Cpu(s) : 0.000000 Cpu(s) : 0.000000 spin_func start!! Cpu(s) : 1.980198 Cpu(s) : 100.000000 Cpu(s) : 101.000000 Cpu(s) : 100.000000 Cpu(s) : 100.000000 spin_func end ... timesec:[5.006046] mutex_func start!! Cpu(s) : 94.059406 Cpu(s) : 0.000000 Cpu(s) : 0.000000 Cpu(s) : 0.000000 Cpu(s) : 0.990099 mutex_func end ... timesec:[5.004011] gurest $
スレッドを使う場合、同期処理は必ず使うと言って良いですが、その同期処理にスピンを使うとCPUを無駄使いする事になります。反面、状況によっても変わりますが、mutexを使うよりかは若干早く切り替わるようです。
しかし生成するスレッドの数が実行している環境のCPU数よりも多いと、すぐに性能が悪化する等、使用方法は限定的です。性能の章で数値を参照してください。
ちなみに、spin_lockは以下のような感じで実装されていると思われます。
typedef pthread_mutex_t wrap_spinlock_t; int wrap_spin_init( wrap_spinlock_t * lock, int type ) { if( type == PTHREAD_PROCESS_PRIVATE ) { return pthread_mutex_init( lock, 0 ); } else { pthread_mutexattr_t attr; pthread_mutexattr_init( &attr ); pthread_mutexattr_setpshared( &attr, type ); return pthread_mutex_init( lock, &attr ); } } int wrap_spin_lock( wrap_spinlock_t * lock ) { int rtn; while( 1 ) { rtn = pthread_mutex_trylock( lock ); if( rtn != EBUSY ) { break; } } return rtn; } int wrap_spin_unlock( wrap_spinlock_t * lock ) { return pthread_mutex_unlock( lock ); }
上記プログラムをspin_mutex.cに組み込んで実行したら、同様にCPU使用率が100%になりました。断言できませんが、恐らくmutex_trylockをwhile文内でグルグル回してるんでしょう、多分。
ダウンロードファイルには上記をspin_mutex.cに組み込んだプログラム(wrap_spin.c)を入れてますので、試してみてください。
