Linux のスレッド間通信/スレッド同期方法 (ミューテックス ロック、読み書きロック、スピン ロック、セマフォ、条件変数、シグナルなど) を理解するための 1 つの記事。

目次

スレッド間通信・スレッド同期方式

ロック機構

ミューテックス

読み取り/書き込みロック (rwlock)

スピンロック(スピン)

セマフォの仕組み

条件変数のメカニズム

信号


スレッド間通信・スレッド同期方式

ps. 以下の段落の多くは、マークダウンの「引用」形式を使用せずに直接引用しており、出典は公開されています。

参考/引用:

プロセス変数リソースはスレッド間で共有されるため、スレッド間の通信の目的は主にスレッドの同期 (つまり、複数のスレッドの実行を制限する順序規則 (セマフォ) または相互排他規則 (ミューテックス ロック)) です。プロセスとは異なります 通信におけるデータ交換に使用される通信メカニズム。

ミューテックス ロック、条件変数、セマフォの違い:

  • 相互排他ロック: 相互排他。あるスレッドが特定のリソースを占有している場合、他のスレッドはそのリソースにアクセスできません。このスレッドのロックが解除された場合にのみ、他のスレッドはそのリソースにアクセスできます。

  • セマフォ: 同期 1 つのスレッドがアクションを完了すると、セマフォを介して他のスレッドに通知し、他のスレッドが特定のアクションを実行します。そして、セマフォにはさらに強力な機能があり、セマフォはリソース カウンタとして使用できます。セマフォの値は、特定のリソースの現在使用可能な番号に初期化され、1 つ使用すると減少し、1 を返すと増加します。

  • 条件変数: 同期 1 つのスレッドがアクションを完了すると、条件変数を通じて他のスレッドにシグナルが送信され、他のスレッドは特定のアクションを実行します。条件変数はミューテックス ロックとともに使用する必要があります。

ロック機構は、アトミック操作に似た状況に適しています。ロック後、特定のクリティカル セクションのリソースがすぐに処理され、すぐにロックが解除されます。長期間のロックには適していません (最悪の場合、クリティカル セクションのリソースが失われることもあります)。 (ロック後のセクションはブロックされます)、割り込みの実行後、割り込みをブロックしてロックする必要があり、その後デッドロックが発生して信号がスタックします)、シグナル/セマフォ (および条件変数) は、長時間の待機に適しています。発生する信号/条件と、呼び出し元は待機/ブロック期間中スリープします。

エラーや複雑さを軽減するために、プログラムを設計する前に、割り込みにロックやシグナルなどを使用しないことを検討し、割り込みプログラムを簡潔かつエレガントに設計する必要があります。

ロック機構

ミューテックス

ミューテックス ロック/ミューテックスは、複数のスレッドが同時に重要なリソースにアクセスするのを防ぐために使用されます。

ミューテックスは本質的にはロックであり、共有リソースにアクセスする前にロックされ、アクセスが完了すると解放されます。ミューテックスがロックされた後、現在のスレッドがミューテックス ロックを解放するまで、ミューテックスを再度ロックする他のスレッドはブロックされます。ミューテックス ロックの場合、リソースがすでに占有されている場合、リソースの申請者はスリープ状態にしか入ることができません。

ミューテックスが解放されたときに複数のスレッドがブロックされている場合、ロック上でブロックされているすべてのスレッドが実行可能になります。実行可能になった最初のスレッドがミューテックスをロックでき、他のスレッドはミューテックスがまだロックされているときのみ実行可能になります。このようにして、一度に 1 つのスレッドのみが順方向に実行できます。

つまり、複数のスレッドがグローバル変数などの重要なリソースにアクセスしたい場合、相互にアクセスする必要があります。私がアクセスすると、あなたはアクセスできなくなります。

ヘッダー ファイル: #include <pthread.h>.

一般的に使用される API:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); // 初期化量
/* この関数はミューテックスを初期化します。最初のパラメータはミューテックス ポインタ、2 番目のパラメータはミューテックスを制御する属性です。通常は NULL です。関数が成功すると、ミューテックスが正常に初期化されたことを示す 0 が返されます。
    もちろん、初期化されたミューテックスはマクロを呼び出すことで素早く初期化することもできます コードは次のとおりです: 
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER; 
*/ 
int
pthread_mutex_lock(pthread_mutex_t *mutex); // Lock (block) 
int pthread_mutex_unlock(pthread_mutex_t *mutex ); // ロック解除 (ノンブロッキング) 
/* ロック関数とロック解除関数はそれぞれロック関数とロック解除関数であり、初期化された pthread_mutex_t ミューテックス ポインタを渡すだけで済みます。成功すると 0 が返されます。
* 
/
    実行権を取得したスレッドはロック関数を実行し、ロックの取得に成功すると、リソースを取得したスレッドがロック解除関数を実行するまで、他のスレッドはロック関数に遭遇するとブロックされます。ロック解除関数は、ミューテックスを待っている他のスレッドを起動します。
    特別な注意は、ロックを取得した後、論理処理が完了してからロック解除を実行する必要があり、そうしないとデッドロックが発生します。その結果、残りのスレッドはブロックされ、実行できなくなります。ミューテックスを使用する場合は、デッドロックを防ぐために pthread_cancel 関数の使用に特に注意してください。
int pthread_mutex_trylock(pthread_mutex_t *mutex); // ミューテックス ロック (ノンブロッキング) 
/* この関数はスレッド ロック関数でもありますが、この関数はノンブロッキング モードにあり、戻り値を使用してロックが成功したかどうかを判断します。使用方法は上記と同じです。ブロック機能とロック機能は一貫しています。*/ 
int
pthread_mutex_destroy(pthread_mutex_t *mutex); // ミューテックスを破棄する
/* この関数はミューテックスを破棄するために使用され、ミューテックスのポインタを渡すことでミューテックスの破棄が完了し、正常に 0 が返されます。 . . */

ルーチン: 参照pthread库-线程编程例程-来自百问网\01_文档配套源码\Pthread_Text10.c

ミューテックス ロックのプロパティ:

/* スレッド属性と同様に、最初に変数を宣言し、次に pthread_mutexattr_init で初期化し、
    次に pthread_mutexattr_getxxx/pthread_mutexattr_setxxx を使用して属性の特定のオプションを取得/設定し、
    次にミューテックス ロックを呼び出すときに属性を入力して pthread_mutex_init を初期化し、
    最後にそれを破棄* / 
int pthread_mutexattr_init(pthread_mutexattr_t* attr); 
int pthread_mutexattr_destroy(pthread_mutexattr_t* attr); 
int
pthread_mutexattr_getshared(const pthread_mutexattr_t* attr, int* pshared); int pthread_mutexattr_setshared(pthread_ mutexattr _t* attr, int* pshared) ; 
pshared
    パラメータPTHREAD_PROCESS_SHARED:
        ミューテックス ロックはプロセス間で共有可能
        PTHREAD_PROCESS_PRIVATE: 初期化スレッドが属するプロセス内のスレッドのみで共有可能
int pthread_mutexattr_gettype(const pthread_mutexattr_t* attr, int* type);
int pthread_mutexattr_settype(pthread_mutexattr_t* attr, int type);
    渡せる型パラメータ渡される: 
        PTHREAD_MUTEX_NOMAL: 公平なロック、すでにロックされている通常のロックを再度ロックするとデッドロックが発生します; 他のスレッドによってロックされている通常のロックのロックを解除する、またはすでにロックされている通常のロックのロックを解除する通常のロックを再度実行すると、予期しない結果が生じる可能性があります。
        PTHREAD_MUTEX_ERRORCHECK: エラー チェック ロック すでにロックされているエラー チェック ロックが再度ロックされると、ロック操作は EDEADLOCK を返します。別のスレッドによってロックされているエラー チェック ロックをロック解除するか、すでにロックが解除されているエラー チェック ロックを再度ロック解除する場合、ロック解除操作は EPERM を返します。
        PTHREAD_MUTEX_RECURSIVE: ネストされたロック、エラー使用により EPERM が返される
        PTHREAD_MUTEX_DEFAULT: 公称と同様。
読み取り/書き込みロック (rwlock)

 読み取り/書き込みロックはミューテックスに似ていますが、読み取り/書き込みロックの方が並列性が高くなります。ミューテックスはロックまたはロック解除され、一度に 1 つのスレッドのみがロックできます。読み取り/書き込みロックには、読み取りモードのロック状態、書き込みモードのロック状態、およびロック解除状態の3 つの状態があります。

書き込みモードで読み取り/書き込みロックを保持できるスレッドは一度に 1 つだけですが、読み取りモードでは複数のスレッドが同時に読み取り/書き込みロックを保持できます。読み取り/書き込みロックが書き込みロック状態にある場合、ロックをロックしようとするすべてのスレッドは、ロックがロック解除されるまでブロックされます。読み取り/書き込みロックが読み取りロック状態にある場合、そのロックを読み取りモードでロックしようとするすべてのスレッドはアクセスできますが、書き込みモードでロックをロックしようとするスレッドは、すべてのスレッドがロックを解除するまでブロックされます。読み取りロック。

ヘッダー ファイル: #include <pthread.h>.

一般的に使用される API:

int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *rwlockattr); // 読み取りおよび書き込みロックを初期化
int
pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); // 読み取りモード ロック読み取りおよび書き込みロック
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); // 書き込みモードロックread Write lock 
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); // 読み取りおよび書き込みロックのロックを解除
int
pthread_rwlock_destroy(pthread_rwlock_t *rwlock); // 読み取りおよび書き込みロックを破棄
スピンロック(スピン)

ミューテックスが占有 (ロック) されており、別のスレッドが入った場合、ミューテックスによってスレッドの切り替えが行われます (待機ではなく、一度譲歩して他のスレッドを実行します)。ロックに適したコンテンツが多数あります。

スピン ロックの場合、ロックが占有されている (ロックされている) 場合、受信スレッドはロックの取得が完了するまで待機します (while(1);死ぬまで待機する、フル タイム スライスで実行する)。ロックに適したコンテンツが比較的小さく、スレッド切り替えのコストが待機コストよりはるかに大きい場合は、スピン ロックを使用します。(RTOS についてある程度の知識があれば、イールド、フル タイム スライスの実行などの概念が理解できるでしょう。マイクロコントローラーで FreeRTOS と RTT をプレイすると理解できます)。

スピン ロックはミューテックスに似ていますが、スリープしてプロセスをブロックするのではなく、ロックを取得する前にビジー待機 (スピン) ブロック状態のままになります。スピン ロックは、次のような状況で使用できます。ロックが短期間保持され、スレッドが再スケジュールにあまりコストをかけたくない場合です。

実行可能スレッドが競合している (すでに保持されている) スピン ロックを取得しようとすると、スレッドは常に待機中、スピン中、つまりアイドル状態になり、ロックが再び利用可能になるのを待っています (ロックが解除されるのを待っています)。スピン ロックが競合すると、そのロックを要求しているスレッドがロックが再び利用可能になるのを待機中にスピンすることになり、CPU 時間が無駄になるため、スピン ロックを長期間保持すべきではありません。実際、これは軽量のロックを短時間で実行するというスピン ロック設計の本来の目的です。スピン ロックは割り込みプログラムでは使用できません。スピン ロックが保持されている間はプリエンプションは無効になります (カーネルのプリエンプションは許可されません)。

ヘッダー ファイル: #include <pthread.h>.

一般的に使用される API:

int pthread_spin_init(pthread_spinlock_t *lock, int pshared); 
/* スピン ロック初期化関数、その中に属性変数はありません、pshared はパラメータを選択できます: PTHREAD_PROCESS_PRIVATE
    このロックはこのプロセスでのみ使用します
    PTHREAD_PROCESS_SHARED このロックは共有メモリ オブジェクトに配置される可能性があります、「複数のプロセス間の共有」の
    スピン ロックを使用して複数のプロセスを同期する場合は、pshared を PTHREAD_PROCESS_SHARED に設定し、プロセス共有メモリに pthread_spinlock_t オブジェクトを割り当てます (pthread_mutex_t にも同じことが当てはまります)。
*/ 
int
pthread_spin_destroy(pthread_spinlock_t *lock); 
//
スピンロック操作
int pthread_spin_lock(pthread_spinlock_t *lock); 
int pthread_spin_trylock(pthread_spinlock_t *lock); 
int pthread_spin_unlock(pthread_spinlock_t *lock);

セマフォの仕組み

セマフォは 0 以上の量です。0 の場合、スレッドはブロックされます。sem_pos 関数 (非ブロッキング) を使用して、セマフォを 1 ずつ増やします。 sem_wait 関数 (ブロッキング) は、セマフォが 0 より大きい場合に 1 ずつ減らし、0 に等しい場合にブロックします。

通知やカウントに使用できます。

  • セマフォを使用すると、本来はランダムかつ順序どおりに実行されない複数のスレッドの実行順序を、制御可能かつ予測可能な方法で制御できます。たとえば、スレッド A が何かを待機しており、スレッド B はその処理が完了した後にスレッド A にシグナルを送信できます。

  • 「セマフォ」オブジェクトは、共有リソースを使用できるスレッドの数を制限するためにカウンターが必要な場合に使用できます。セマフォには、指定されたリソースに現在アクセスしているスレッドのカウント値が保存され、そのカウント値は、そのリソースをまだ使用できるスレッドの数です。このカウントがゼロに達すると、このセマフォによって制御されるリソースへのすべてのアクセス試行はキューに入れられ、タイムアウトになるかカウント値がゼロ以外になるまで待機します。

ヘッダー ファイル: #include <semaphore.h>.

一般的に使用される API:

int sem_init(sem_t *sem, int pshared, unsigned int value); //セマフォを初期化する
/* この関数はセマフォを初期化でき、最初のパラメータは sem_t 型ポインタで渡されます。
    0 で渡される 2 番目のパラメータはスレッド制御 (複数のスレッド内で使用) を表し、それ以外の場合はプロセス制御 (複数のプロセスで使用) を表します。
    3 番目のパラメータはセマフォの初期値を表し、0 はブロッキングを表し、1 以上の数値は初期値 (非ブロッキング) を表します。
    セマフォの初期化が完了し、実行が成功すると 0 が返されます。*/ 
int sem_destroy(sem_t * sem); // セマフォを破棄
int
sem_wait(sem_t * sem); // セマフォを 1 減らす (ブロック)、P 操作
int sem_post(sem_t * sem); // セマフォを増やすby 1 (非ブロッキング), V Operation 
int sem_trywait(sem_t *sem); // セマフォを 1 だけ減らします (非ブロッキング)、正常に 0 を返します
/* sem_wait 関数は、指定されたリソースが利用可能かどうかを検出するために使用されます。セマフォ。利用可能なリソースがない場合はブロックされます。リソースが利用可能な場合は、sem - 1 操作が自動的に実行されます。
   sem_post 関数は、指定されたセマフォのリソースを解放し、sem + 1 演算を実行します。
   つまり、セマフォの適用と解放*/
返さ
int sem_timedwait (sem_t * sem,const struct timespec * abstime); // sem_wait の待機/ブロック期間中に最大 abstime 秒待機し、 int 
sem_post_multiple (sem_t * sem,int count); // セマフォ数を 1 回増加します (ノンブロッキング) 
int
sem_getvalue(sem_t * sem, int * sval); // 現在のセマフォの値を取得します

ルーチン: リファレンスpthread库-线程编程例程-来自百问网\01_文档配套源码\Pthread_Text12.cpthread库-线程编程例程-来自百问网\02_视频配套源码\pthread3.c 和 pthread4.c

条件変数のメカニズム

条件変数は、条件が満たされたこと、つまりイベントが発生するまでスレッドが一時停止されたことを他のスレッドに通知するために使用される同期メカニズムです。一般的には共有データの状態情報を相手に通知するために使用されるため、条件変数はミューテックスと組み合わせて使用​​されます。

条件変数は、マルチスレッド プログラムで「待機 ---> ウェイクアップ」ロジックを実装するために一般的に使用される方法です。

ヘッダー ファイル: #include <pthread.h>.

一般的に使用される API:

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); // 条件変数を初期化します
/* 属性を設定する必要がない場合は、cond_attr に N ULL を入力します */ 
/* pthread_cond_t をデフォルトで初期化する別の方法 cond = PTHREAD_COND_INITIALIZER; */ 
int pthread_cond_destroy (pthread_cond_t *cond); // 条件変数を破棄
int
Pthread_wait (pthread_cond_t *cond, pthread_mutex_t *mutex); t PTHread_Cond_Timewait (pthread_cond_t *cond, pthread_mutex_t *mutex 
, const struct timeSpec *TSPTR ); // 指定された時間内、条件変数が true になるのを待ちます
int
pthread_cond_signal(pthread_cond_t *cond); // 条件が満たされたことを条件変数に通知します。pthread_cond_signal 関数は、cond 条件変数を待っているスレッドのみを起動します。 Use : send 
Signal
 
    :
     
    pthread_mutex_lock (&mutex); 最初にロックします
        ここで重要なリソースを変更し、次にリソースを変更します
    pthread_cond_signal(&cond); 次に、条件変数シグナルを送信します
    pthread_mutex_unlock(&mutex); 次に、ロックを解除し
てシグナルを待ちます
    : 
    pthread_mutex_lock(&mutex); 最初にロック/* ロックできるときにロックし、次に後で実行し、それ以外の場合は block */ 
    pthread_cond_wait(&cond, &mutex); そして、条件変数 signal を待ちます/* 条件が満たされない場合、ミューテックスは最初にロック解除され、次にここでブロック/スリープして条件が満たされるのを待ちます満たされる; 条件が満たされるとウェイクアップされ、最初にミューテックスをロックし、次にブロックを解除して後で実行します*/
        ここで重要なリソースを変更し、次にリソース
    pthread_mutex_unlock(&mutex); を変更してからロックを解除します
    ...

ルーチン: 参照pthread库-线程编程例程-来自百问网\02_视频配套源码\pthread5.c

信号

プロセス内の複数のスレッド間の信号の処理は、プロセス内の信号処理とは異なり、より多くの学習と使用が必要です。

たとえば、プロセスに SIGSTP シグナルを送信すると、プロセスのすべてのスレッドが停止します。すべてのスレッドが同じメモリ空間を使用するため、シグナルのハンドラーは同じですが、異なるスレッドは異なる管理構造を持っているため、異なるスレッドは異なるマスクを持つことができます (Linux スレッド実装と LinuxThread vs. NPTL & ユーザーレベル カーネルから引用)レベルのスレッドとスレッドと信号処理 - blcblc - Blog Park (cnblogs.com) )。

参考:

おすすめ

転載: blog.csdn.net/Staokgo/article/details/132630769