[Linuxアプリケーションプログラミング] POSIXスレッドの相互排他および同期メカニズムの条件変数



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を参照しください

おすすめ

転載: blog.csdn.net/qq_20553613/article/details/106287968