3.同期(3/4):read/write lock
3.5 read/write lock
スレッドが保護対象を参照する時、変更しようとしているスレッドはロックされていないといけませんが、参照しようとしている他のスレッドはロックされていなくてもいいはずです。逆に変更しようとしているスレッドが複数居ても、各スレッドでロックされていないといけません。
mutexやspinでは、保護対象の情報に対して上記のような柔軟性を持っておらず、全スレッドで排他されてしまい、ひいてはボトルネックになります。
キャッシュ情報へのアクセス等は情報の参照だけであれば複数スレッドによる同時処理でも問題ないはず。それを実現するのが、read/write lockです。
- int pthread_rwlock_init( pthread_rwlock_t * __rwlock, __const pthread_rwlockattr_t * __attr )
- int pthread_rwlock_destroy( pthread_rwlock_t * __rwlock )
- int pthread_rwlock_rdlock( pthread_rwlock_t * __rwlock )
- int pthread_rwlock_tryrdlock( pthread_rwlock_t * __rwlock )
- int pthread_rwlock_timedrdlock( pthread_rwlock_t * __rwlock, __const struct timespec * __abstime )
- int pthread_rwlock_wrlock( pthread_rwlock_t * __rwlock )
- int pthread_rwlock_trywrlock( pthread_rwlock_t * __rwlock )
- int pthread_rwlock_timedwrlock( pthread_rwlock_t * __rwlock, __const struct timespec * __abstime )
- int pthread_rwlock_unlock( pthread_rwlock_t * __rwlock )
pthread_rwlockattr_t * __attrはspinと同様、プロセスをまたいで使用するかの属性を設定します。使用しない場合はNULLを設定し、自プロセス内のスレッドにのみ使用する設定になります。
下記サンプルプログラムを動かします。write_funcはwrite_countを変更し、read_funcはwrite_countを参照しています。read_funcのカウンタは高速で進みますが、write_funcは1秒毎でwrite_lockします。write_lockされるとread_funcのカウンタが止まり、write_funcがunlockすると、read_funcが再開しますが、カウンタの末尾のアルファベットが変更されています。
なお、write_funcはread_funcの生存確認をpthread_killで行ってますが、この関数の説明は別称で行います。ちなみにスレッド生成の際、怠けて数値を直接変数として設定しているため、ワーニングが出ます。手を抜いてすいません。
/* gcc rwlock_test.c -o rwlock_test -g -W -Wall -lpthread */ #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #include <signal.h> #define LOOP_CNT 20000000 pthread_rwlock_t lock; int write_count = 0x41; /* 'A' */ pthread_t id[4]; void * read_func ( void * arg ); void * write_func( void * arg ); int main( ) { pthread_rwlock_init( &lock, 0 ); pthread_create( &id[0], 0, read_func, ( void * )0 ); pthread_create( &id[1], 0, read_func, ( void * )1 ); pthread_create( &id[2], 0, read_func, ( void * )2 ); pthread_create( &id[3], 0, write_func, ( void * )3 ); pthread_join( id[0], 0 ); pthread_join( id[1], 0 ); pthread_join( id[2], 0 ); pthread_join( id[3], 0 ); pthread_rwlock_destroy( &lock ); return 0; } void * read_func( void * arg ) { int index = ( int * )arg; int count = 0; int i; for( i = 0; i < LOOP_CNT; i ++ ) { pthread_rwlock_rdlock( &lock ); count ++; if( count % 1000 == 0 ) { printf( "\t\t%d %*.*s%5d[%c]\r", index, index + 1, index + 1, "\t\t\t", count / 1000, write_count ); } pthread_rwlock_unlock( &lock ); } printf( "thread %d End!!\n", index ); return 0; } void * write_func( void * arg ) { int index = ( int * )arg; int i; for( i = 0; ; i ++ ) { sleep( 1 ); if( pthread_kill( id[0], 0 ) && pthread_kill( id[1], 0 ) && pthread_kill( id[2], 0 )) { break; } pthread_rwlock_wrlock( &lock ); printf( "\nnow wrlock!! %c -> %c\n", write_count, write_count + 1 ); write_count ++; sleep( 1 ); pthread_rwlock_unlock( &lock ); } printf( "thread %d End!!\n", index ); return 0; }
尚、read/write lockもそれ程難しくなく自作できます。
init/read lock/read unlock/write lock/write unlock を自作しました。プログラムが多少長いので詳細はダウンロードファイル(wrap_rwlock.c)を参照願います。詳細は下記のような感じです。
- read_lock
- read_unlock
- write_lock
- write_unlock
実際にプログラム(wrap_rwlock.c)を動かした結果もpthread_rwlock_*系関数と同じ動きになります。destroyやtrylockの自作を試しても良いと思います。
