並列分散ソフトウェア 電子・情報工学系 新城 靖 <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.hlla.is.tsukuba.ac.jp/~yas/sie/pdsoft-2003/2004-01-22
あるいは、次のページから手繰っていくこともできます。
http://www.hlla.is.tsukuba.ac.jp/~yas/sie/
http://www.is.tsukuba.ac.jp/~yas/index-j.html
http://www.hlla.is.tsukuba.ac.jp/~yas/index-j.html
auto変数のスコープは、関数内。
auto変数より広い範囲で使えるスレッドごとのデータが必要になる。
tsd[thread_id][key];
pthread_key_create()
pthread_setspecific()
pthread_getspecific()
---------------------------------------------------------------------- 1: 2: /* 3: * tsd-counter.c 4: * Start: 2001/01/08 08:51:25 5: */ 6: 7: #include <stdio.h> /* stderr */ 8: #include <stdlib.h> /* malloc() */ 9: #include <pthread.h> 10: 11: struct tsd_counter 12: { 13: int tc_value ; 14: }; 15: 16: static pthread_once_t tsd_counter_alloc_once = PTHREAD_ONCE_INIT ; 17: static pthread_key_t tsd_counter_tsdkey ; 18: 19: static void 20: tsd_counter_tsdkey_init() 21: { 22: extern void free(void *ptr); 23: pthread_key_create( &tsd_counter_tsdkey,free ); 24: } 25: 26: static struct tsd_counter * 27: tsd_counter_alloc() 28: { 29: struct tsd_counter *tc ; 30: 31: pthread_once(&tsd_counter_alloc_once,tsd_counter_tsdkey_init); 32: tc = malloc( sizeof(struct tsd_counter) ); 33: if( tc == 0 ) 34: { 35: fprintf(stderr,"no memory for struct tsd_counter\n"); 36: exit( -1 ); 37: } 38: memset( tc, 0, sizeof(struct tsd_counter) ); 39: if( pthread_setspecific( tsd_counter_tsdkey, tc ) != 0 ) 40: { 41: fprintf(stderr, "pthread_setspecific().\n"); 42: exit( -1 ); 43: } 44: return( tc ); 45: } 46: 47: static struct tsd_counter * 48: tsd_counter_gettsd() 49: { 50: struct tsd_counter *tc ; 51: tc = pthread_getspecific( tsd_counter_tsdkey ); 52: if( tc == 0 ) 53: { 54: tc = tsd_counter_alloc(); 55: } 56: return( tc ); 57: } 58: 59: void tsd_counter_reset( int val ) 60: { 61: struct tsd_counter *tc ; 62: tc = tsd_counter_gettsd(); 63: tc->tc_value = val ; 64: } 65: 66: void tsd_counter_up() 67: { 68: struct tsd_counter *tc ; 69: tc = tsd_counter_gettsd(); 70: tc->tc_value ++ ; 71: } 72: 73: int tsd_counter_getvalue() 74: { 75: struct tsd_counter *tc ; 76: tc = tsd_counter_gettsd(); 77: return( tc->tc_value ); 78: } ----------------------------------------------------------------------TSD 利用のパタン
pthread_getspecific()
の呼出しを節約するため。
pthread_key_create()
で
初期化する。確実に一度だけ初期化するために、pthread_once()
を使
う。
pthread_getspecific()
でポインタを得る。
malloc()
して、初期化して、
pthread_setspecific()
で登録する。
pthread_key_delete()
は、普通、使われない。ダイナミック・リンク
のモジュールを unload する時に必要になる(かもしれない)。
---------------------------------------------------------------------- 1: 2: /* 3: * tsd-counter-main.c by Y.Shinjo 4: * Start: 2001/01/08 08:51:25 5: */ 6: 7: #include <pthread.h> 8: 9: void thread1( int x ); 10: void thread2( int x ); 11: 12: extern void tsd_counter_reset( int val ); 13: extern void tsd_counter_up(); 14: extern int tsd_counter_getvalue(); 15: 16: main() 17: { 18: pthread_t t1 ; 19: pthread_t t2 ; 20: pthread_create( &t1, NULL, (void *)thread1, (void *)10 ); 21: pthread_create( &t2, NULL, (void *)thread2, (void *)20 ); 22: printf("main()\n"); 23: pthread_join( t1, NULL ); 24: pthread_join( t2, NULL ); 25: } 26: 27: void thread1( int x ) 28: { 29: int i ; 30: tsd_counter_reset( x ); 31: printf("thread1(): value=%d \n", tsd_counter_getvalue() ); 32: for( i = 0 ; i<3 ; i++ ) 33: { 34: tsd_counter_up(); 35: printf("thread1(): value=%d \n", tsd_counter_getvalue() ); 36: } 37: } 38: 39: void thread2( int x ) 40: { 41: int i ; 42: tsd_counter_reset( x ); 43: printf("thread1(): value=%d \n", tsd_counter_getvalue() ); 44: for( i = 0 ; i<3 ; i++ ) 45: { 46: tsd_counter_up(); 47: printf("thread2(): value=%d \n", tsd_counter_getvalue() ); 48: } 49: } ----------------------------------------------------------------------実行例。
---------------------------------------------------------------------- % wget http://www.hlla.is.tsukuba.ac.jp/~yas/sie/pdsoft-2003/2004-01-22/ex/tsd-counter.c% wget http://www.hlla.is.tsukuba.ac.jp/~yas/sie/pdsoft-2003/2004-01-22/ex/tsd-counter-main.c
% wget http://www.hlla.is.tsukuba.ac.jp/~yas/sie/pdsoft-2003/2004-01-22/ex/Makefile
% make tsd-counter-main
gcc -D_REENTRANT -g -mcpu=v8 -Dpthread_setconcurrency=thr_setconcurrency -Dpthread_getconcurrency=thr_getconcurrency -c tsd-counter-main.c -o tsd-counter-main.o gcc -D_REENTRANT -g -mcpu=v8 -Dpthread_setconcurrency=thr_setconcurrency -Dpthread_getconcurrency=thr_getconcurrency -c tsd-counter.c -o tsd-counter.o gcc -D_REENTRANT -g -mcpu=v8 -Dpthread_setconcurrency=thr_setconcurrency -Dpthread_getconcurrency=thr_getconcurrency -o tsd-counter-main tsd-counter-main.o tsd-counter.o -lpthread % ./tsd-counter-main
main() thread1(): value=10 thread1(): value=11 thread1(): value=12 thread1(): value=13 thread1(): value=20 thread2(): value=21 thread2(): value=22 thread2(): value=23 %
----------------------------------------------------------------------
スレッド固有データの特徴
ただし意味が違うので、mutex と相互に書き換えできないことがある。 プロセス全体のデータを扱うものは、mutex で書くしかない。 Time Zoneなど。
スレッドごとに乱数生成器を持ってもいいのか。 再現性が必要か。mutex では、再現性はない。
ライブラリを実現する場合、自分自身では Runnable を定義できないことがあ る。その時には、ThreadLocal クラスを使って、Pthread と似たようなことをする。
---------------------------------------------------------------------- Java Pthread ---------------------------------------------------------------------- final pthread_once() java.lang.ThreadLocal() pthread_key_create() get() pthread_getspecific() set() pthread_setspecific() initialValue() - (GC) pthread_key_delete() ----------------------------------------------------------------------
図? 開いたモジュールと閉じたモジュール
export している関数の入口で lock, 出口 unlockを入れて、 スレッド・セーフなモジュールを作りたい。 export している関数が、他の export している関数を呼び出すと、デットロッ クになる。---------------------------------------------------------------------- 1: /* 2: * mutex-reclock-normal.c 3: * Start: 2001/01/08 09:41:11 4: */ 5: 6: #include <pthread.h> 7: 8: void thread_A(), thread_B(); 9: int shared_resource ; 10: pthread_mutex_t mutex1 ; 11: 12: deposit( int n ) 13: { 14: pthread_mutex_lock( &mutex1 ); 15: shared_resource += n ; 16: pthread_mutex_unlock( &mutex1 ); 17: } 18: 19: add_interest() 20: { 21: int i ; 22: pthread_mutex_lock( &mutex1 ); 23: i = shared_resource * 0.05 ; 24: deposit( i ); 25: pthread_mutex_unlock( &mutex1 ); 26: } 27: 28: main() { 29: pthread_t t1 ; 30: pthread_t t2 ; 31: shared_resource = 1000000 ; 32: pthread_mutex_init( &mutex1, NULL ); 33: 34: pthread_create( &t1, NULL, (void *)thread_A, 0 ); 35: pthread_create( &t2, NULL, (void *)thread_B, 0 ); 36: pthread_join( t1, NULL ); 37: pthread_join( t2, NULL ); 38: printf("main(): shared_resource == %d\n", shared_resource ); 39: } 40: 41: void thread_A() 42: { 43: printf("thread_A(): deposit( 10000 ) ... \n"); 44: deposit( 10000 ); 45: printf("thread_A(): deposit( 10000 ) done. \n"); 46: } 47: 48: void thread_B() 49: { 50: printf("thread_B(): add_interest() ... \n"); 51: add_interest(); 52: printf("thread_B(): add_interest() done. \n"); 53: } ----------------------------------------------------------------------実行例。
---------------------------------------------------------------------- % wget http://www.hlla.is.tsukuba.ac.jp/~yas/sie/pdsoft-2003/2004-01-22/ex/mutex-reclock-normal.c% wget http://www.hlla.is.tsukuba.ac.jp/~yas/sie/pdsoft-2003/2004-01-22/ex/Makefile
% make mutex-reclock-normal
gcc -D_REENTRANT -g -mcpu=v8 -Dpthread_setconcurrency=thr_setconcurrency -Dpthread_getconcurrency=thr_getconcurrency -c mutex-reclock-normal.c -o mutex-reclock-normal.o gcc -D_REENTRANT mutex-reclock-normal.o -lpthread -lposix4 -o mutex-reclock-normal % ./mutex-reclock-normal
thread_A(): deposit( 10000 ) ... thread_A(): deposit( 10000 ) done. thread_B(): add_interest() ... ^C %
----------------------------------------------------------------------
---------------------------------------------------------------------- 1: /* 2: * mutex-reclock-recursive.c 3: * Start: 2001/01/08 09:41:11 4: */ ... 28: static int 29: my_pthread_mutex_init_recursive( pthread_mutex_t *mutex ) 30: { 31: pthread_mutexattr_t attr ; 32: int err ; 33: if( (err=pthread_mutexattr_init( &attr )) < 0 ) 34: return( 0 ); 35: if( (err=pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE)) <0 ) 36: return( 0 ); 37: err = pthread_mutex_init( mutex,&attr ); 38: return( err ); 39: } 40: 41: main() 42: { 43: pthread_t t1 ; 44: pthread_t t2 ; 45: shared_resource = 1000000 ; 46: my_pthread_mutex_init_recursive( &mutex1 ); 47: 48: pthread_create( &t1, NULL, (void *)thread_A, 0 ); 49: pthread_create( &t2, NULL, (void *)thread_B, 0 ); 50: pthread_join( t1, NULL ); 51: pthread_join( t2, NULL ); 52: printf("main(): shared_resource == %d\n", shared_resource ); 53: } ... ----------------------------------------------------------------------実行例。deposit() と add_interest() のタイミングによっては、最終結果は 違うことがある。
---------------------------------------------------------------------- % wget http://www.hlla.is.tsukuba.ac.jp/~yas/sie/pdsoft-2003/2004-01-22/ex/mutex-reclock-recursive.c% wget http://www.hlla.is.tsukuba.ac.jp/~yas/sie/pdsoft-2003/2004-01-22/ex/Makefile
% make mutex-reclock-recursive
gcc -D_REENTRANT -g -mcpu=v8 -c mutex-reclock-recursive.c -o mutex-reclock-recursive.o gcc -D_REENTRANT mutex-reclock-recursive.o -lpthread -lrt -o mutex-reclock-recursive % ./mutex-reclock-recursive
thread_A(): deposit( 10000 ) ... thread_A(): deposit( 10000 ) done. thread_B(): add_interest() ... thread_B(): add_interest() done. main(): shared_resource == 1060500 %
----------------------------------------------------------------------
---------------------------------------------------------------------- printf("hello,world\n"); ----------------------------------------------------------------------上と下は、結果が違う。
---------------------------------------------------------------------- printf("hello,"); /* ここに他のスレッドの出力が混じることがある */ printf("world\n"); ----------------------------------------------------------------------これを避けるには、
flockfile(),funlockfile(),ftrylockfile()
を使う。
---------------------------------------------------------------------- flockfile(stdout); printf("hello,"); /* ここに他のスレッドの出力が混じることはない */ printf("world\n"); funlockfile(stdout); ----------------------------------------------------------------------
putchar()
や
getchar()
は、遅すぎる。
flockfile()/funlockfile()
の中で使うための
putchar_unlocked(),getchar_unlocked(),putc_unlocked(),getc_unlocked()
が用意されている。printf_unsafe()
はない。
これを実現するものが、「複数読み手単一書き手ロック」、あるいは、 「読み書きロック」。
初期の Pthread には、この機能はなかった。新しい標準で採り入れられた。
次のような機能がある。
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr); int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER; int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); int pthread_rwlockattr_init(pthread_rwlockattr_t *attr); int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);sakura (SunOS 5.6) には、pthread_rwlock_rdlock() はない。その代りに rw_rdlock() を使う。pthread_rwlock_rdlock() は、SunOS の rw_rdlock() が Pthread 標準に取り込まれたものと思われる。
Dijkstra, Brinch Hansen にり提唱。Hoare によって発展。
Binch Hansen の Concurrent Pascal、N.Wirth の Modula, R.C.Holt の CSP/k、Xerox社の Mesa、Hoare Simula 。
モニタ内部の関数が呼び出されると、自動的にロックされる。 リターンすると解除される。
モニタ名: monitor begin 局所変数 procedure モニタ名(引数の並び); begin 本体 end 局所手続き 初期化のコード end
利点
Java のメソッド単位の synchronized は、モニタを実現したものである。 synchronized ブロックは、モニタではない。
基本的には、スレッドのプログラムでUnix のシグナル(ソフトウェア割込みの 扱い)を使ってはいけない。 マルチスレッドは、ソフトウェア割込みを、より使いやすいものに置き換える ためのもの。
シグナルは、SIGKILL や SIGSTOP などプロセス全体を操作するためだ けに使うべきである。
どうしても必要な時:
pthread_sigmask()
を使ってマスクする。
sigwait()
, sigtimewait()
を呼び出すよ
うなシグナルを待つ専用のスレッドを作る。
fork()
の意味が不明
どうしても fork()
する時必要がある時には、fork() の時に
必要な特殊な処理を行なう手続きを書き、pthread_atfork()
で登録する。
execve() には問題がない。 ただし、プロセス間共有メモリを使っている時には、共有メモリ上の mutex をアンロックする。
例:2つの mutex を2つのスレッドが次のようなタイミングでロックしよう とすると、デッドロックになる。
---------------------------------------------------------------------- thread_A() thread_B() : : pthread_mutex_lock( &mutex1 ); pthread_mutex_lock( &mutex2 ); : : pthread_mutex_lock( &mutex2 ); pthread_mutex_lock( &mutex1 ); ----------------------------------------------------------------------
解決方法(1): 複数の mutex をロックする時に順序を決める。
上の場合、どのスレッドも mutex1
, mutex2
の
順でロックを行なうと、デッドロックは起こらない。
解決方法(2): だめな時には最初からやりなおす。
pthread_mutex_trylock()
を使って、失敗したら全部をアンロッ
クして最初からやり直す。
ライブロックの危険性がある。
ロックよりも楽にプログラムを作りたい。
特に分散では。集中でも、フォールト・トレランスの実現では必要。
歴史:1960年代、テープの時代。 失敗したら、巻き戻す。
銀行口座間の預金の移動。
注意:Javaの serializable とは意味が全く違う。
複数のトランザクションは、並行に実行可能。しかし、その複数のトランザク ションの最終結果は、それらを「ある順序」で逐次的に実行した時と同じ結果 になる。
逆に、トランザクションは、直列化可能なスケジュールが存在するなら、並行 に実行してもよい。直列化可能でないなら、トランザクションは、開始を遅ら せるか、アボートする。
Begin_Transaction(); reserve("NRT-SFO"); reserve("SFO-JFK"); Commit();プログラムの記述としては、これだけ。どこにもロックは出てこない。内部的 には、ロックは行われる(ことがある)。
途中の reserve() が失敗したら、トランザクション全体を abort() する。
シャドー・コピーを作らず、ファイルのその場所を書き換える。 その前に、次の情報を安定記憶に書き込む。
クラッシュからの回復もできる。
2種類のプロセス
もっとも単純な方法: Begin Transaction で、ファイルをロックする。 Commit/Abort でアンロックする。
注意:プログラマは、ロックを意識しない。
制約が強すぎる。
読み取りロック:read lock と write lock を分ける。
ロックの粒度(granularity)が大事
実際のシステムでは、Commit/Abort するまで、一度確保したロックをはなさない。 第2相で、まとめて全部ロックを解放する(strict two-phase locking)。
利点
注意:2相ロックと2相コミットは別。
実現(衝突時の対応):プライベート作業空間を使う実現が便利。 どんどんプライベート作業空間上のファイルを更新して、最後にコミットする か削除する。
性質
前提:
直列化可能性という制限はきつすぎる。
kill では済まないものもある。
トランザクションが使える場合には、回復は単に abort すればよい。
対策:タイムスタンプを使う。
プローブ・メッセージ:3つ組
改良:プローブメッセージに待っているプロセスのリストを付ける。 リストの中で犠牲となるプロセスを一意に決める。
理論的には、改善の余地が大きい。
大域的な時間とトランザクションを利用した方法(1):待機消滅(wait-die)型 アルゴリズム
大域的な時間とトランザクションを利用した方法(2):負傷待機(wound-wait) 型アルゴリズム
Java で利用可能なセマフォを実現しなさい。
次のライブラリ関数は、static 変数に結果を保存して返すので、スレッド・ セーフではない。
getlogin() readdir() strtok() asctime() ctime() gmtime() localtime()
rand() getgrgi() getgrnam() getpwuid() getpwnam()
これらのライブラリ関数を、スレッド固有データを使うように修正しなさい。 この時、引数の数と型、結果の型は、スレッド・セーフでないものと同じにな るようにしなさい。ただし、名前には、_tsd というサフィックスを付け、ス レッド・セーフではないものと区別すること。実現では、それぞれのリエント ラント版を使ってもよい。たとえば、getlogin_tsd() の実現において、 getlogin_r() を使ってよい。
実現した関数と、mutex を使う方法との性能を比較しなさい。
銀行口座として、普通口座と定期口座の2つの口座を考える。その2つの口座 の総合残高を返す手続きを、読み書きロックを使って書きなさい。総合残高以 外にも、それぞれの口座は、単独に預入、引出しができるようにすること。 2つの銀行口座の間で、デットロックが起きないように預金を移動させる手続 きを実現しなさい。