QNX学習ノート4より多くの同期機構More on synchronization

28394 ワード

前に紹介しました.
mutexsemaphore;Barrierという三つの同期の仕組み
システムには、以下の4つの中の同期機構がある.
readers/writer lock;スリープロック;condition variable;additional Neutrine service
一、readers/writer lock(rwlock)
一度に一つのスレッドだけがデータ構造を書き込み操作することができることは明らかである.任意の数のスレッドはスレッドを読むことができますが、注意すべき点は、データを読み込む時に同時にデータを書き込むことができないことです.
rwlockの初期化と廃棄関数は以下の通りです.
int
pthread_rwlock_init (pthread_rwlock_t *lock,
const pthread_rwlockattr_t *attr);

int
pthread_rwlock_destroy (pthread_rwlock_t *lock);
atrは、NULL値を割り当ててデフォルトパラメータを表すことができる.
初期化されていないものや破壊されているrwlockを使ってはいけません.
rwlockを使う場合は、以下の2つの基本モードがあります.
a reader will want “non-exclusive” access

a writer will want “exclusive” access
これに対応して、以下の4つの関数が使用できます.
int
pthread_rwlock_rdlock (pthread_rwlock_t *lock);
int
pthread_rwlock_tryrdlock (pthread_rwlock_t *lock);
int
pthread_rwlock_wrlock (pthread_rwlock_t *lock);
int
pthread_rwlock_trywrlock (pthread_rwlock_t *lock);
tryバージョンについては、長い間説明しましたが、よく分かりませんでした.blockが成功しないときはtryバージョンを使うと強制的にblockが作られます.
最後に、ロックをかけて任務を執行したらロックを解除します.マンホールに立ってウンチをしないでください.次の関数を使います.
int
pthread_rwlock_unlock (pthread_rwlock_t *lock);
他のスレッドがロック解除を待っている時、状態表示はREADYです.
なお、mutexではrwlockができないのはrwlockであり、複数のreaderが読み取り操作を行うことが保証され、mutexが実行されると一つのreaderだけが許可される.semaphoreはrwlockの2つの基本モードを区別できません.semaphoreを使うと複数のreaderと一つ以上のwriterの悪い状況が現れます.
二、スリープロック
マルチスレッドプログラムには、あるイベントが発生するまで、1つのthreadが待っている場合があります.
相関関数は以下の通りです.
int
pthread_sleepon_lock (void);
int
pthread_sleepon_unlock (void);
int
pthread_sleepon_broadcast (void *addr);
int
pthread_sleepon_signal (void *addr);
int
pthread_sleepon_wait (void *addr);
これらはPOSIX規格に合わないことに注意してください.
マニュアルの一例は、
One thread is a producer thread that’s getting data from some piece of hardware.The other thread is a consumer thread that’s dong some form of processing on the data that jurst arrived.
一つのスレッドがハードウェアからデータを取得し、他のスレッドが取得したデータを処理する.
対応する関数は以下の通りです.
consumer ()
{
	while (1) {
		pthread_sleepon_lock ();
		while (!data_ready) {
			pthread_sleepon_wait (&data_ready);
		}
		// process data
		data_ready = 0;
		pthread_sleepon_unlock ();
	}
}
producer ()
{
	while (1) {
		// wait for interrupt from hardware here...
		pthread_sleepon_lock ();
		data_ready = 1;
		pthread_sleepon_signal (&data_ready);
		pthread_sleepon_unlock ();
	}
}
分かりやすいと思いますが、いずれにしてもsignalとwaitの二つの関数はとても巧みで、スレッド間のメッセージ伝達という感じがします.とても不思議です.
三、condition variable
これは前のsleepon lockと似ていますが、実はsleepon lockはcondarのベースになっていると言われています.
彼らの違いはマニュアルで紹介されているように、sleepon lockにmutexを隠していますが、condviarを使う時はmutexを使う必要があります.下は本の中の一例です.感じも簡単です.どうせメインスレッドが他のサードを切っても牛を追い詰めても大丈夫です.
#include 
#include 
int data_ready = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t condvar = PTHREAD_COND_INITIALIZER;
void *
consumer (void *notused)
{
 printf ("In consumer thread...
"
); while (1) { pthread_mutex_lock (&mutex); while (!data_ready) { pthread_cond_wait (&condvar, &mutex); } // process data printf ("consumer: got data from producer
"
); data_ready = 0; pthread_cond_signal (&condvar); pthread_mutex_unlock (&mutex); } } void * producer (void *notused) { printf ("In producer thread...
"
); while (1) { // get data from hardware // we’ll simulate this with a sleep (1) sleep (2); printf ("producer: got data from h/w
"
); pthread_mutex_lock (&mutex); while (data_ready) { pthread_cond_wait (&condvar, &mutex); } data_ready = 1; pthread_cond_signal (&condvar); pthread_mutex_unlock (&mutex); } } main () { printf ("Starting consumer/producer example...
"
); // create the producer and consumer threads pthread_create (NULL, NULL, producer, NULL); pthread_create (NULL, NULL, consumer, NULL); // let the threads run for a bit sleep (5); }
signal vs broadcast
DVDはsleepon lockのお父さんですから、DVDもあります.
pthread_cond_signal()&pthread_cond_broadcast()
この二つのもの
この違いを説明する必要があります.
実際には文字どおりの意味も多少なりとも感じられます.
signalは特別で、一回に1つのthreadしか起動できません.マルチスレッドプログラムの中で一番優先度が高いのは起動されます.同じ優先順位のthreadが複数あると、場面が混乱します.制御できません
broadcast放送は、すべてのthreadが実行されていることを通知します.これは待ちスレッドを起動することができます.以下は本で紹介されたbroadcastを使用する例です.(一部のコード)
#include 
#include 
pthread_mutex_t mutex_xy = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cv_xy = PTHREAD_COND_INITIALIZER;
int x, y;
int isprime (int);
thread1 ()
{
for (;;) {
pthread_mutex_lock (&mutex_xy);
while ((x > 7) && (y != 15)) {
pthread_cond_wait (&cv_xy, &mutex_xy);
}
// do something
pthread_mutex_unlock (&mutex_xy);
}
}
thread2 ()
{
for (;;) {
pthread_mutex_lock (&mutex_xy);
while (!isprime (x)) {
pthread_cond_wait (&cv_xy, &mutex_xy);
}
// do something
pthread_mutex_unlock (&mutex_xy);
}
}
thread3 ()
{
for (;;) {
pthread_mutex_lock (&mutex_xy);
while (x != y) {
pthread_cond_wait (&cv_xy, &mutex_xy);
}
// do something
pthread_mutex_unlock (&mutex_xy);
}
}
注意したいのは、各threadの中でwhileの条件が違います.なぜこの仕組みを使うのかというと、cのスイッチ・ケースで代用できるような気がします.でも、これらを同期の下に置くと、Windowsはこの面でQnx同期性がないといいかもしれません.
sleepon vs condar
この部分は私もよく分かりません.分かりましたら補充してください.
四、additional Neutrino service
thread poolsスレッド池はやはりイメージを比較する仕組みですね.
一定数量のthreadがblocking状態にあることを制御できます.また、一定数量のthreadがprocessing状態にあります.
以下はthread poolの相関関数と変数とそれらを使うヘッダファイルです.
#include 

thread_pool_t *
thread_pool_create (thread_pool_attr_t *attr,
                    unsigned flags);

int
thread_pool_destroy (thread_pool_t *pool);

int
thread_pool_start (void *pool);

int
thread_pool_limits (thread_pool_t *pool,
                    int lowater,
                    int hiwater,
                    int maximum,
                    int increment,
                    unsigned flags);

int
thread_pool_control (thread_pool_t *pool,
                     thread_pool_attr_t *attr,
                     uint16_t lower,
                     uint16_t upper,
                     unsigned flags);
使い方も基本的に簡単です.
先thread_pool_create()
そしてthread_pool_start()
あとでいいです.普通はthread_は使いません.pool_destroy()あなたのプログラムは永遠に実行されます.
その中にthread_pool_limits()は、thread poolの属性を指定し、atrのパラメータを調整するための関数であり、thread_pool_control()はその簡略版である.
atrは本当に複雑なデータ構造です.具体的な状況は以下の通りです.
typedef struct _thread_pool_attr {
    // thread pool functions and handle
    THREAD_POOL_HANDLE_T    *handle;

    THREAD_POOL_PARAM_T
        *(*block_func)(THREAD_POOL_PARAM_T *ctp);

    void
        (*unblock_func)(THREAD_POOL_PARAM_T *ctp);

    int
        (*handler_func)(THREAD_POOL_PARAM_T *ctp);

    THREAD_POOL_PARAM_T
        *(*context_alloc)(THREAD_POOL_HANDLE_T *handle);

    void
        (*context_free)(THREAD_POOL_PARAM_T *ctp);

    // thread pool parameters
    pthread_attr_t          *attr;
    unsigned short          lo_water;
    unsigned short          increment;
    unsigned short          hi_water;
    unsigned short          maximum;
} thread_pool_attr_t;
*atrは前に述べたthreadのデータ構造と一致していますが、優先順位スタックサイズなどがありますか?
‘lo’waterが一番少ないのはblocking状態のthread数です.
incrementがthreadがlo_を下回ればwaterではincrementを作成します.
hiwaterが一番多いのはblocking状態のthread数です.
maximm thread poolスレッドの池に最も多く存在するスレッド数(blockingとprocessingの状態でthreadの数の合計)
そのいくつかの関数の役割とctp handleデータ転送は以下のような疑似コードで説明できます.
 FOREVER DO
    IF (#threads < lo_water) THEN
        IF (#threads_total < maximum) THEN
            create new thread
            context = (*context_alloc) (handle);
        ENDIF
    ENDIF
    retval = (*block_func) (context);
    (*handler_func) (retval);
    IF (#threads > hi_water) THEN
        (*context_free) (context)
        kill thread
    ENDIF
DONE
blocking状態にあるthreadの数がlo_を下回るわけではないことが分かります.water関数は新しいスレッドを作成します.これは同時にthreadの総数がmaximumより小さいという条件を満たしています.