1はじめに
では前の記事、私は主に意味、属性、使用の原理、およびミューテックスロックの使用方法を説明しました。ミューテックスロックは、スレッド間の相互排除にのみ使用でき、「同期」には使用できないという欠点があります。そのため、ミューテックスは独立して使用されないことがよくあります。この記事では、条件変数の関連コンテンツについて説明します。条件変数はミューテックスロックと組み合わせて使用されることが多いため、このように使用される理由については、以下の章で詳細を参照してください。
2条件変数
条件変数(条件変数)は、スレッド間の同期のためのメカニズムです。条件変数は本質的にセマフォです。スレッドは特定の条件の待機をブロックします。取得されない状態変数のシグナルが取得されるまでブロックされた場合、別のスレッドが予期される条件に到達すると、待機スレッドへの通知シグナルを生成し、待機スレッドは起動して実行を継続し、同期を達成します。
2.1条件変数の特性
- スレッド同期に使用
- スレッドをスリープさせることができる、スレッドは条件変数信号を待たずにスリープ状態に入る
2.2条件変数の適用シナリオ
- プロデューサーコンシューマーモデル
2.3条件変数の使用の原則
- 一緒に使用される条件変数とミューテックスロック
- Mutexロックは通常のロックを使用し、ネストされたロックは使用できません
- 条件変数はコピーできませんが、そのポインターはコピーできます
- 条件変数と述語の関係は、通常1:1または1:Nです。デッドロックが発生しやすいN:1は避けてください。
- スレッドが条件変数を取得するのを待って、特に1:Nの場合に、述語が再度確立されているかどうかを手動で確認する必要があります
[1]ウェイクアップがインターセプトされる可能性があります。システムの同時実行により、複数のスレッドが条件変数を待機している場合、スレッドAが条件変数を取得する前に、スレッドBが共有データの状態を取得してリセットします。述語が無効になるため、スレッドAが述語をチェックしない場合、予期しない結果(nullポインターへのアクセスなど)が発生する可能性があります。
[2]プログラムの堅牢性を高める
条件変数の述語:
述語は、一意の真と偽の値を持つステートメントを表します。述語は、プログラムで現在のスレッドに必要な条件付き状態を記述するために使用できます。条件変数の焦点は、共有データの状態の変更であり、この変更は述語で説明できます。述語が偽の場合、条件変数のシグナルを待ち、条件変数を取得した後、述語を真または偽と判断して、タスクを実行するかどうかを決定します。
3条件変数の使用
条件変数を使用するための基本的な手順は次のとおりです。
[1]条件変数インスタンスを作成します
[2]条件変数を初期化します
[3]条件変数を待っています
[4]条件変数信号を送信します
[5]条件変数を破棄します
3.1条件変数を作成する
posixスレッドmutex pthread_cond_t
は、データ構造によって表されます。条件変数インスタンスは、静的および動的に作成できます。
pthread_cond_t cond;
3.2条件変数を初期化する
条件変数の初期化では、pthread_cond_init
動的初期化またはマクロを PTHREAD_COND_INITIALIZER
使用して静的初期化を実現できます。これPTHREAD_COND_INITIALIZER
は、POSIXで定義されている構造定数です。
動的初期化
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
-
cond
、条件変数インスタンスのアドレスはNULLにできません -
attr
、条件変数属性アドレス、およびNULLは、デフォルト属性を使用することを意味します。ほとんどの場合、デフォルト属性を使用します。属性の詳細については、セクション4を参照してください。 -
戻り、成功した場合は0を返し、パラメーターが無効な場合はEINVALを返します。
静的初期化
マクロを使用したPTHREAD_COND_INITIALIZER
静的初期化メソッドはpthread_cond_init
、デフォルト属性(attr
着信NULL)を使用した動的初期化と同等ですが、PTHREAD_COND_INITIALIZER
マクロは関連するエラーパラメータをチェックしないという違いがあります。
使用例:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
3.3条件変数の取得(待機)
条件変数の取得は、無限ブロックモードと指定タイムアウトブロックモードに分けられます。
3.3.1ブロッキング方法
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
cond
、条件変数インスタンスのアドレスはNULLにできませんmutex
、mutexインスタンスのアドレスはNULLにできません- 戻り、成功した場合は0を返し、パラメーターが無効な場合はEINALを返します。
3.3.2タイムアウトブロックモードを指定する
int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex,
const struct timespec *abstime);
cond
、条件変数インスタンスのアドレスはNULLにできませんmutex
、mutexインスタンスのアドレスはNULLにできませんabstime
、タイムアウト時間、単位はクロックビート- 返す
戻り値 | 解説 |
---|---|
0 | 成功 |
EINVAL | 無効な引数 |
EDEADLK | ミューテックスは、関数を繰り返し呼び出すネストされていないロックです |
忙しい | ロックは別のスレッドによって保持されています |
ETIMEDOUT | タイムアウト |
この関数は無期限に待機するのではなく、指定された時間のビートの後、関数は条件変数シグナル関数が戻るまで待機せずに戻りますETIMEDOUT
。
3.3.3 mutexと組み合わせて使用
システムの同時実行性のため、条件変数の待機はミューテックスロックと一緒に使用する必要があります。条件変数の取得を待機しているときに、複数のスレッドが競合します。条件変数信号も共有リソースであり、排他的アクセスが必要であることも理解できます。参照擬似コードを待っているミューテックス:
pthread_mutex_lock(&mutex)
while(/* 谓语 */)
{
pthread_cond_wait(&cond, &mutex);
}
if (/* 谓语成立 */)
{
/* todo */
}
pthread_mutex_unlock(&mutex);
3.4状態信号の生成
条件信号の生成は、ポイントツーポイントモードとブロードキャストモードに分かれています。ポイントツーポイントモードは、条件変数を待機している1つのスレッドのみをウェイクアップでき、ブロードキャストモードは、条件変数を待機しているすべてのスレッドをウェイクアップできます。
3.4.1ポイントツーポイントモード
int pthread_cond_signal(pthread_cond_t *cond);
cond
、条件変数インスタンスのアドレスはNULLにできません- 戻り、成功した場合は0を返し、パラメーターが無効な場合はEINALを返します。
3.4.2ブロードキャストモード
int pthread_cond_broadcast(pthread_cond_t *cond);
cond
、条件変数インスタンスのアドレスはNULLにできません- 戻り、成功した場合は0を返し、パラメーターが無効な場合はEINALを返します。
3.4.3 mutexロックと組み合わせて使用
条件変数信号が生成される前に、共有リソースへのアクセスをロックして、共有リソースを保護する必要があります。参照擬似コードの一般的な使用:
pthread_mutex_lock(&mutex);
/* todo 访问共享资源 */
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
さらに、待機中のスレッドと条件付き信号生成スレッドはロックされており、もう1つの重要な機能は、信号の損失を防ぐことです。ロック信号を生成するスレッドが最初に実行され、条件信号を生成する場合、待機スレッドはこの時点では実行されておらず、条件信号が「失われ」、条件信号が失われます。ロックの場合、条件付き信号生成スレッドは、信号を生成する前にロックを申請する必要があります。
シグナリング機能の場所の問題
- ロック後
pthread_mutex_lock(&mutex);
/* todo 访问共享资源 */
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
利点:潜在的なパフォーマンス低下の問題がない
欠点:スレッド優先順位の逆転の問題があり、低い優先順位が高い優先順位のスレッドよりも優先されます。ロックを解除してシグナルを送る前に、mutexロックを保持するために適用されている低優先度のスレッド(待機中のセマフォスレッドよりも低い優先度)がある場合、スレッドが最初に起こされてスケジュールされ、高優先度の待機中のセマフォスレッドがプリエンプトされます。 。
pthread_mutex_lock(&mutex);
/* todo 访问共享资源 */
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
- ロック間
利点:スレッド優先順位の逆転の問題がない
デメリット:パフォーマンス低下の問題が発生し、効率が低下する
一般に、2番目の方法をお勧めします。
3.5条件変数の破棄
int pthread_cond_destroy(pthread_cond_t *cond);
cond
、条件変数インスタンスのアドレスはNULLにできません- 返す
戻り値 | 解説 |
---|---|
0 | 成功 |
EINVAL | condが破棄されたか、condが空です |
忙しい | 条件変数は他のスレッドで使用されています |
pthread_cond_destroy
動的に初期化された条件変数を破棄するために使用されます。破棄後の条件変数は初期化されていない状態であり、条件変数の属性と制御ブロックパラメーターは使用できない状態です。destroy関数を使用するときに注意すべき点がいくつかあります。
- 破棄された条件変数は
pthread_cond_init
再初期化できます - 破棄された条件変数を繰り返し破棄することはできません
- マクロ
PTHREAD_COND_INITIALIZER
を使用して静的に初期化されたミューテックスロックは破棄できません - スレッドが条件変数(ブロッキングスレッド、シグナル生成)を使用していない場合、それを破棄できます。
3. 6例を書く
コード実現関数:
- 「生産者-消費者」モデルを作成する
- 2つのスレッドを作成します。1つはデータの生成を担当し、もう1つはデータを読み取って端末に出力します。
- 2つのスレッドがメモリを共有し、条件変数によって同期されます
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include "pthread.h"
struct _buff_node
{
uint8_t buf[64];
uint32_t occupy_size;
};
/* 静态方式创建初始化互斥锁和条件变量 */
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
/* 共享缓存 */
static struct _buff_node s_buf;
void *thread0_entry(void *data)
{
/* 消费者线程 */
uint8_t i =0;
for (;;)
{
pthread_mutex_lock(&mutex);
while (s_buf.occupy_size == 0)
{
pthread_cond_wait(&cond, &mutex);
}
if (s_buf.occupy_size != 0)
{
printf("consume data:");
}
for (i=0; i<s_buf.occupy_size; i++)
{
printf("0x%02x ", s_buf.buf[i]);
}
s_buf.occupy_size = 0;
pthread_mutex_unlock(&mutex);
printf("\r\n");
}
}
void *thread1_entry(void *data)
{
/* 生产者线程 */
uint8_t i =0;
for (;;)
{
pthread_mutex_lock(&mutex);
for (i = 0;i<8; i++)
{
s_buf.buf[i] = rand(); /* 随机生成数据 */
s_buf.occupy_size++;
}
pthread_mutex_unlock(&mutex);
printf("produce %d data\n", s_buf.occupy_size);
pthread_cond_signal(&cond);
sleep(1); /* 1秒生成一组数 */
}
}
int main(int argc, char **argv)
{
pthread_t thread0,thread1;
void *retval;
pthread_create(&thread0, NULL, thread0_entry, NULL);
pthread_create(&thread1, NULL, thread1_entry, NULL);
pthread_join(thread0, &retval);
pthread_join(thread1, &retval);
return 0;
}
出力結果
acuity@ubuntu:/mnt/hgfs/LSW/STHB/pthreads/cond$ gcc cond.c -o cond -lpthread
acuity@ubuntu:/mnt/hgfs/LSW/STHB/pthreads/cond$ ./cond
produce 8 data
consume data:0x67 0xc6 0x69 0x73 0x51 0xff 0x4a 0xec
produce 8 data
consume data:0x29 0xcd 0xba 0xab 0xf2 0xfb 0xe3 0x46
コードでは、printf
関数がロックされており、実際の使用は許可されていないため、ロックの原則に違反します。これは単なるシミュレーションシナリオのテストです。
4条件変数の属性
デフォルトの条件変数属性を使用すると、ほとんどのアプリケーションシナリオに対応でき、条件変数属性を特別なシナリオに合わせて調整することもできます。主な条件変数の属性とAPIを以下に示します。条件変数属性を設定する基本的な手順は次のとおりです。
[1]条件変数属性インスタンスを作成します
【2】初期プロパティインスタンス
【3】属性設定
[4]プロパティインスタンスの破棄
4.1条件変数プロパティを作成する
POSixスレッド条件変数属性は、pthread_condattr_t
データ構造によって表されます。条件変数属性インスタンスは、静的および動的に作成できます。
pthread_condattr_t attr;
4.2条件変数属性の初期化と破棄
int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);
attr
、条件変数属性インスタンスのアドレスはNULLにできません- 成功した場合は0を返し、パラメータが無効な場合はEINVALを返します
条件変数のプロパティを設定するときは、最初にプロパティpthread_condattr_t
インスタンスを作成し、次にpthread_condattr_init
関数の初期インスタンスを呼び出してから、プロパティ設定を呼び出します。初期化後の属性値は、デフォルトの条件変数属性です。これはpthread_cond_init
、デフォルトの属性でattr
初期化を使用することと同じです(NULLを渡します)。
4.3条件変数のスコープ
条件変数のスコープは、条件変数のスコープを表します。これは、インプロセス(作成者)スコープPTHREAD_PROCESS_PRIVATE
とプロセス間スコープに分かれていますPTHREAD_PROCESS_SHARED
。インプロセススコープはプロセス内のスレッド同期にのみ使用でき、プロセス間はシステムのすべてのスレッド間の同期に使用できます。
スコープ設定と取得機能
int pthread_condattr_setshared(pthread_condattr_t *attr,int pshared);
int pthread_condattr_getshared(pthread_condattr_t *attr,int *pshared);
attr
、条件変数属性インスタンスのアドレスはNULLにできませんpshared
、スコープタイプ、PTHREAD_PROCESS_PRIVATE
およびPTHREAD_PROCESS_SHARED
- 成功した場合は0を返し、パラメータが無効な場合はEINVALを返します
注:条件変数と組み合わせて使用されるミューテックスロックの
スコープは一貫している必要があり、変更も同期的に変更する必要があります。
4.4条件変数のクロックタイプ
条件変数クロック種別はpthread_cond_timewait
、条件変数関数タイムアウトパラメータの計算クロックを待つタイムアウト時間を指定するために使用されます。
時計タイプ | 解説 |
---|---|
CLOCK_REALTIME | 設定可能なシステムリアルタイムクロック |
CLOCK_MONOTONIC | 設定不可能なシステムリアルタイムクロック |
CLOCK_PROCESS_CPUTIME_ID | プロセスCPU時間 |
CLOCK_THREAD_CPUTIME_ID | スレッドCPU時間 |
時計種類設定・取得機能
int pthread_condattr_setclock(pthread_condattr_t *attr,clockid_t clock_id);
int pthread_condattr_getclock(const pthread_condattr_t *attr,clockid_t *clock_id);
attr
、条件変数属性インスタンスのアドレスはNULLにできませんclock_id
、時計タイプ、上記マクロとして設定/取得可能- 戻る、正常に0を返す
5まとめ
条件変数はスレッド間の同期のメカニズムであり、mutexロックと組み合わせて使用されることがよくあります。したがって、使用プロセスもデッドロック問題に注意を払う必要があり、条件変数とミューテックスロックの使用をベンチマークとして使用する必要があります。条件変数の使用の原則についてはセクション2.3を、ミューテックスロックの使用の原則については前回の記事のセクション2.3を参照してください。