Linux システムのプログラミング ---- マルチスレッドの詳細な説明

マルチスレッド

スレッド (LWP: 軽量プロセス)、Linux 環境では、スレッドの本質は依然としてプロセスであり、最下層にスレッドを記述するための特別な構造はありません。軽量プロセスとも呼ばれます。すべてのプロセスには、少なくとも 1 つの実行ストリーム (スレッド) があります。


概要

ハードディスク上のプログラムが実行されると、PCB (プロセス制御ブロック) がメモリ空間に形成され、各プロセスは独自の仮想アドレス空間 (mm_struct) を持ちます。プロセスはオペレーティング システムのリソース割り当てを担当するエンティティであり、スレッドは CPU の最小実行単位です
スレッドはプロセス内に存在します (プロセスはスレッド コンテナと見なすことができます)。プロセスは、特定のデータ セットで実行中のアクティビティに関する特定の独立した機能を持つプログラムです。プロセスは、システムが割り当ててスケジュールするための独立した単位です。資力。
スレッドは、プロセスの実体であり、CPU のスケジューリングとディスパッチの基本単位であり、プロセスよりも小さく、独立して実行できる基本単位です。スレッド自体は基本的にシステム リソースを所有せず、動作に必要なリソース (プログラム カウンター、一連のレジスター、スタックなど) しか持っていませんが、プロセスが所有するすべてのリソースを同じプロセスに属する他のスレッドと共有できます。 .
各プロセスには独自のアドレス空間があり、プロセスのアドレス空間はスレッド間で共有されます. プロセス内のほとんどのリソースは、スレッドによってアクセスされる権利を持っています.



スレッド履歴

Linux は元々、カーネル内のスレッドを実際にはサポートしていませんでした。ただし、clone() システム コールを使用して、プロセスをスケジュール可能なエンティティとして処理できます。この呼び出しは、呼び出しプロセスと同じアドレス空間を共有する呼び出しプロセスのコピーを作成します。LinuxThreads プロジェクトは、この呼び出しを使用して、ユーザー空間全体でスレッドのサポートをシミュレートします。残念ながら、このアプローチには、特にシグナル処理、スケジューリング、およびプロセス間同期プリミティブに関して、いくつかの欠点があります。さらに、このスレッド モデルは POSIX 要件に準拠していません。
LinuxThreads を改善するには、カーネル サポートが必要であることは明らかであり、スレッド ライブラリを書き直す必要があります。これらの要件を満たすために、2 つの競合するプロジェクトが開始されました。IBM の開発者を含むチームが NGPT (Next-Generation POSIX Threads) プロジェクトを開始しました。同時に、Red Hat の一部の開発者は NPTL プロジェクトに取り組みました。NGPT は 2003 年半ばに放棄され、分野は完全に NPTL に委ねられました。
NPTL (ネイティブ POSIX スレッド ライブラリ) は、Linux スレッドの新しい実装であり、LinuxThreads の欠点を克服し、POSIX の要件も満たしています。LinuxThreads と比較して、パフォーマンスと安定性の両方が大幅に向上しています。

スレッド ライブラリのバージョンを確認します: getconf GNU_LIBPTHREAD_VERSION



スレッドの目的

  • マルチスレッドを適切に使用すると、CPU を集中的に使用するプログラムの実行効率を向上させることができます
  • I/O タイプのプログラム (Web ページの読み込み、Web ページのレンダリング、画像のダウンロード、および応答の相互作用を担当するために複数のスレッドを必要とするなど) のユーザー エクスペリエンスを向上させることができます。

スレッドのプライベート データ:

  • スレッドID
  • レジスタのセット。スタック
  • エラー番号
  • ブロックされたテーブル (シグナル マスク ワード)
  • スケジューリングの優先度 (pri)

スレッド間で共有

  • 同一アドレス空間内でコード部分とデータ部分を共有し、関数を定義すればスレッドごとに呼び出し可能、グローバル変数を定義すればスレッドごとにアクセス可能。
  • ファイル記述子テーブル
  • ハンドラ テーブル (信号処理メソッド)
  • 現在の作業ディレクトリ
  • ユーザー ID とグループ ID


スレッドの長所と短所


アドバンテージ

  • 新しいスレッドの作成は、新しいプロセスの作成よりもはるかに安価です
  • プロセス間の切り替えと比較して、スレッド間の切り替えに必要なオペレーティング システムの作業ははるかに少なく、スレッドはプロセスよりもはるかに少ないリソースを占有します。
  • 並列化可能な数のマルチプロセッサを最大限に活用する
  • 遅い I/O 操作が終了するのを待っている間、プログラムは他の計算タスクを実行できます。

欠点

  • ライブラリ関数、不安定
  • デバッグ、書き込みが困難、gdb をサポートしていない
  • 弱い信号サポート



POSIX スレッド ライブラリ

  • スレッド関連の関数は完全なシリーズを構成し、ほとんどの関数の名前は「pthread_」で始まります
  • これらの関数ライブラリを使用するには、ヘッダー ファイル <pthread.h> を導入します。
  • これらのスレッド関数ライブラリをリンクする場合は、「-lpthread」オプションを追加してください

指定された LWD 番号を表示します: ps -Lf pid



スレッド操作機能


各スレッドには独自のスレッド番号 (tid) があり、各スレッドにもスレッド番号があります。プロセス番号はオペレーティング システム全体で一意であり、スレッド番号はそのプロセス内でのみ有効です。
プロセス番号は、正の整数である pid_t データ型で表されます。スレッド番号は pthread_t データ型で表され、Linux は unsigned long integer を使用します。
一部のシステムでは、pthread_t を実装するときに構造体を使用して pthread_t を表すため、移植可能なオペレーティング システムの実装では整数として扱うことができません。


pthread_create

NAME
       pthread_create - create a new thread
SYNOPSIS
       #include <pthread.h>
       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
       Compile and link with -pthread.

機能:
スレッドを作成します。
パラメータ:
thread : スレッド番号のアドレス
attr : スレッド属性構造体のアドレス。通常は NULL に設定されます。start_routine: スレッド関数の入り口アドレス。
arg : スレッド関数に渡されるパラメーター。
戻り値: 成功: 0 失敗:: 0 以外


pthread_equal

NAME
       pthread_equal - compare thread IDs
SYNOPSIS
       #include <pthread.h>
       int pthread_equal(pthread_t t1, pthread_t t2);
       Compile and link with -pthread.
DESCRIPTION
       The pthread_equal() function compares two thread identifiers.
RETURN VALUE
       If the two thread IDs are equal, pthread_equal() returns a nonzero value; otherwise, it returns 0.

2 つのスレッドが等しいかどうかを判断し、等しい場合はゼロ以外を返し、等しくない場合は 0 を返します。
移植性のために機能比較を使用してみてください。



pthread_join

NAME
       pthread_join - join with a terminated thread
SYNOPSIS
       #include <pthread.h>
       int pthread_join(pthread_t thread, void **retval);
       Compile and link with -pthread.

関数:

プロセスの wait() 関数と同様に、スレッドが終了するのを待ち (この関数はブロックされます)、スレッド リソースを再利用します。スレッドが終了した場合、関数はすぐに戻ります。
パラメータ:
thread : 待機するスレッド番号。
retval : スレッドの終了ステータスを格納するために使用されるポインターのアドレス。戻り値:
成功: 0 失敗: 0 以外

この関数を呼び出すスレッドはハングし、id が thread であるスレッドが終了するまで待機します。スレッド thread はさまざまな方法で終了し、pthread_join によって取得される終了ステータスも異なります

2. スレッド thread が pthread_cancel を呼び出す別のスレッドによって異常終了した場合、retval が指すユニットに定数 PTHREAD_CANCELED (-1) が格納されます。
3. スレッド thread が pthread_exit 自体を呼び出して終了した場合、retval が指すユニットは pthread_exit に渡されたパラメータを格納します。



pthread_cancel

NAME
       pthread_cancel - send a cancellation request to a thread
SYNOPSIS
       #include <pthread.h>
       int pthread_cancel(pthread_t thread);
       Compile and link with -pthread.

機能:
スレッドをキャンセルします。この操作はリアルタイムではなく、ユーザー モードがカーネル モードに移行するときに実行されます。たとえば、システム コール関数を呼び出します。
パラメータ:
thread : スレッド番号。
戻り値: 正常に 0 を返します エラー: 設定エラー



pthread_detach

NAME
       pthread_detach - detach a thread
SYNOPSIS
       #include <pthread.h>
       int pthread_detach(pthread_t thread);
       Compile and link with -pthread.

一般に、スレッドが終了した後、他のスレッドが pthread_join を呼び出してそのステータスを取得するまで、その終了ステータスは残ります。ただし、スレッドは切り離し状態にすることもでき、このようなスレッドが終了すると、そのスレッドが占有していたすべてのリソースは、終了状態を保持せずにすぐに回収されます。
すでに detach 状態にあるスレッドで pthread_join を呼び出すことはできません。そのような呼び出しは EINVAL エラーを返します。



pthread_exit

NAME
       pthread_exit - terminate calling thread
SYNOPSIS
       #include <pthread.h>
       void pthread_exit(void *retval);
       Compile and link with -pthread.

機能:
呼び出しスレッドを終了します。プロセス内の複数のスレッドがプロセスのデータ セグメントを共有するため、通常、スレッドが占有していたリソースはスレッドの終了後に解放されません。return に相当するパラメータ
:
retval : スレッドの終了ステータスを格納するポインタ。
戻り値:無効

メイン スレッドがデフォルトで終了すると、プロセス全体が終了し、それに応じてすべてのスレッドが破棄されます. 他のスレッドの実行中にメイン スレッドを終了する場合は、メイン関数で pthread_exit() 関数を使用して終了する必要があります.



まとめ

  • ゾンビ スレッドを回避します。新しいスレッドは実行後にリソースを再利用する必要があります。pthread_join と pthread_detach を使用してゾンビ スレッドを防ぎます。

  • 結合されたスレッドは、結合関数が戻る前にすべてのメモリ リソースを解放する可能性があるため、再利用されたスレッドのスタック内の値は返されません。

  • malloc および mmap によって要求されたメモリは、他のスレッドによって解放される可能性があります

  • マルチスレッド モデルで fork を呼び出さない

  • シグナルはマルチスレッドに適していません。マルチスレッド プログラムでシグナルを使用することは避けてください。



スレッドのミューテックス

  • 重要なリソース: マルチスレッド実行ストリームによって共有されるリソースは重要なリソースと呼ばれます
  • クリティカル セクション: 各スレッド内で、クリティカル リソースにアクセスするコードは、クリティカル セクションと呼ばれます。
  • 相互排除: 相互排除は、クリティカル リソースにアクセスするためにクリティカル セクションに入る実行フローが常に 1 つだけであることを保証します。これにより、通常、クリティカル リソースが保護されます。
  • 原子性: スケジューリング メカニズムによって中断されない操作. 操作には、完了または未完了の 2 つの状態しかありません。

ミューテックス

ほとんどの場合、スレッドが使用するデータはローカル変数であり、変数のアドレス空間はスレッド スタック空間にあります.この場合、変数は単一のスレッドに属し、他のスレッドはこの変数を取得できません.
しかし、時には多くの変数をスレッド間で共有する必要があり、そのような変数は共有変数と呼ばれ、スレッド間の相互作用はデータ共有によって完了することができます。
. 複数のスレッドが共有変数を同時に操作するため、いくつかの問題が発生します。

相互排除の概念:

異なるタスク間に散在する多数のプログラムフラグメントを指し、タスクがプログラムフラグメントの 1 つを実行すると、他のタスクはそれらのいずれも実行できず、タスクがプログラムフラグメントの実行を終了するまで待機することしかできません。最も基本的なシナリオは、パブリック リソースを同時に使用できるのは 1 つのプロセスまたはスレッドのみであり、複数のプロセスまたはスレッドがパブリック リソースを同時に使用することはできないというものです。

同期の概念:

同期: 異なるタスク間に散在する多数のプログラム フラグメントを指し、それらの操作は指定された特定の順序に従って厳密に実行する必要があります。この順序は、完了する特定のタスクによって異なります。最も基本的なシナリオは、操作中に 2 つ以上のプロセスまたはスレッドが調整され、あらかじめ決められた順序で実行されるというものです。たとえば、タスク A の動作は、タスク B によって生成されたデータに依存します。

同期は、より複雑な種類の相互排除と考えることができ、相互排除は特別な種類の同期です。相互排除とは、2 つのタスクを同時に実行できないことを意味します。これらは相互に排他的であり、一方のスレッドが実行を完了するまで、もう一方のスレッドを実行する必要があります。同期は同時に実行できませんが、対応するスレッドを特定の順序で実行する必要があります。したがって、相互排除は一意で排他的ですが、相互排除はタスクの実行順序を制限しません。つまり、タスクは順不同ですが、同期されたタスク間には順序関係があります。


相互排他ロック (ミューテックス)

相互排除ロック (mutex)、相互排除ロックは、共有リソースへのアクセスを制御する単純なロック方法であり、相互排除ロックには、ロック (ロック) とロック解除 (ロック解除) の 2 つの状態しかありません。

ミューテックスの操作の流れは次のとおりです。

  • 共有リソースのポスト クリティカル セクションにアクセスする前に、ミューテックスをロックします。
  • アクセスが完了したら、mutex のロックを解放します。
  • ミューテックスがロックされると、ミューテックスを再びロックしようとする他のスレッドは、ロックが解放されるまでブロックされます。

ここに画像の説明を挿入

この時点でスレッドがクリティカル セクションのコードを実行している場合、CPU 実行タイム スライスが到着するか、優先順位の高いスレッドが到着し、スレッドが削除されますが、スレッドは引き続きロックを保持し、他のスレッドは実行できません。クリティカル セクションにアクセスします。



ミューテックス関連の関数


pthread_mutex_init

ミューテックスの初期化に使用されます。

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);

機能:
ミューテックスを初期化します。
パラメータ:
mutex : ミューテックス アドレス。タイプは pthread_mutex_t です。
attr : ミューテックスの属性を設定します。通常、デフォルトの属性を使用できます。つまり、attr を NULL に設定できます。
マクロ PTHREAD_MUTEX_INITIALIZER を使用して、ミューテックスを静的に初期化できます。たとえば: pthread_mutex_tmutex = PTHREAD_MUTEX_INITIALIZER;
このメソッドは、 attr パラメータを NULL で指定して pthread_mutex_init() を呼び出して、動的な初期化を完了するのと同じです。違いは、PTHREAD_MUTEX_INITIALIZER マクロがエラーチェックを実行します。
戻り値: 成功: 0、正常に適用されたロックはデフォルトで開かれます。失敗: ゼロ以外のエラー コード



pthread_mutex_destroy

#include <pthread.h>
int pthread_mutex_destroy(pthread_mutdx_t *mutex);

機能:
指定されたミューテックスを破棄します。ミューテックスを使用した後、ミューテックスを破棄してリソースを解放する必要があります。
パラメータ:
mutex : ミューテックス アドレス。
戻り値:
成功: 0 失敗: ゼロ以外のエラー コード



pthread_mutex_lock

#include <pthread.h>
int pthread_mutex_1ock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);
调用该函数时,若互斥锁未加锁,则上锁,返回 0;
若互斥锁已加锁,则函数直接返回失败,即EBUSY。

機能:
ミューテックスをロックする. ミューテックスが既にロックされている場合、呼び出し元はロックする前にミューテックスがロック解除されるまでブロックします.
パラメータ:
mutex : ミューテックス アドレス。
戻り値:
成功: 0 失敗: ゼロ以外のエラー コード



pthread_mutex_unlock

#include <pthread.h>
int pthread_mutex_un1ock(pthread_mutex_t *mutex);

機能:
指定されたミューテックスのロックを解除します。
パラメータ:
mutex : ミューテックス アドレス。
戻り値:
成功: 0 失敗: ゼロ以外のエラー コード



スレッドの安全性テスト


100 枚のチケットがあると仮定すると、この時点で 4 つのスレッドがチケットを取得しています。

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

int tickets = 100;// 100张票

void* func1(void* arg)
{
    
    
    while(1)
    {
    
    
        if(tickets > 0)
        {
    
    
            usleep(10000);
            printf("抢到第%d张票了,\n", tickets);
            tickets--;
        }
        else
		{
    
    
            return NULL;
        }
    }

    return NULL;
}

int main()
{
    
    
    pthread_mutex_init(&lock, NULL);
    
    pthread_t tid1, tid2, tid3, tid4;
    pthread_create(&tid1, NULL, func1, NULL);
    pthread_create(&tid2, NULL, func1, NULL);
    pthread_create(&tid3, NULL, func1, NULL);
    pthread_create(&tid4, NULL, func1, NULL);

    pthread_mutex_destroy(&lock);
    
    pthread_exit(NULL);
}

i-- はコンパイル後に複数のアセンブリ命令を形成します. CPU がまだ i-- 操作を実行している場合, スレッドはこの時点で取り除かれ, 他のスレッドから見たチケットの値は間違ったものになります. この時点で、スレッド セーフの問題が発生します。この関数は再入不可の関数です。
ここに画像の説明を挿入


ミューテックスを使用してデータを安全に保ちます。

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

int tickets = 100;
pthread_mutex_t lock; //首先所有线程得看到同一把锁

void* func1(void* arg)
{
    
    
    while(1)
    {
    
    
        pthread_mutex_lock(&lock);//加锁
        if(tickets > 0)
        {
    
    
            usleep(10000);
            printf("抢到第%d张票了,\n", tickets);
            tickets--;
            pthread_mutex_unlock(&lock);//解锁
        }
        else{
    
    
            pthread_mutex_unlock(&lock);
            return NULL;
        }
    }

    return NULL;
}

int main()
{
    
    
    pthread_mutex_init(&lock, NULL);
    
    pthread_t tid1, tid2, tid3, tid4;
    pthread_create(&tid1, NULL, func1, NULL);
    pthread_create(&tid2, NULL, func1, NULL);
    pthread_create(&tid3, NULL, func1, NULL);
    pthread_create(&tid4, NULL, func1, NULL);
    
    pthread_mutex_destroy(&lock);
}

相互排他ロックの原理


重要なリソースはミューテックスを使用して保護できますが、最初のロックのプロセスもスレッドセーフである必要があり、1 つのスレッドのみがロックを取得する必要があります。

ほとんどのアーキテクチャは、mutex 操作を実装するための swap または exchange 命令を提供します。この命令の機能は、レジスタとメモリユニットのデータを交換することであり、命令が 1 つしかないため、原子性が保証されます。


疑似コードのロックとロック解除

lock :
	movb $0, %al
	xchgb %al, mutex
	if( al寄存器的内容>0 ) {
    
    
		return 0;
	}else
		挂起等待;
goto lock;

unlock:
	movb $1, mutex
	唤醒等待Mutex的线程;
	return 0;

ロックを獲得するためのアセンブル命令は xchgb の 1 つだけであり、その関数はレジスタ値とメモリ内の値の交換を実現します。このときスレッドAがロックを獲得してxchgbを実行したとすると、このときメモリ内のロックの値は0に置き換えられており、スレッドBは再度ロックを獲得しようとしてxchgbを実行し、置き換え後、 0 になり、スレッド B がブロックされます。この操作はアトミックです。

ロックを取得できるスレッドは 1 つだけなので、ロック解除操作にはスレッド セーフの問題はありません。



デッドロック

デッドロックとは、実行中のリソースの競合や相互の通信により、2 つ以上のプロセスがブロックされる現象で、外力がなければ先に進むことができません。このとき、システムがデッドロック状態にある、またはシステムにデッドロックが発生したといい、これらのプロセスが常に互いに待機している状態をデッドロック プロセスと呼びます。

デッドロックが発生する条件:

  • 相互排除: リソースは、一度に 1 つの実行フローでのみ使用できます
  • 要求と保留: リソースの要求によって実行フローがブロックされた場合、取得したリソースを保留します
  • 剥奪なし: 実行フローによって取得されたリソースは、使い果たされるまで強制的に剥奪することはできません
  • 循環待機: 頭から尾への循環待機リソースを形成する複数の実行フロー間の関係

デッドロックを回避する方法:

  • デッドロックは、デッドロックに必要な 4 つの条件の 1 つまたは複数を破ることによって防ぐことができます。

  • 動的リソース割り当てのプロセスで、システムが何らかの方法で危険な状態にならないようにします。

  • 実行時にデッドロックが発生した場合は、それを検出し、時間内に対処する必要があります。

  • デッドロックが発生した後、プロセスを解放し、通常はプロセスを取り消し、リソースを回復してから、閉塞状態のプロセスに割り当てます。

デッドロック発生

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int s1 = 1,s2 = 1;
pthread_mutex_t lock1, lock2;

void* func1(void* arg)
{
    
    
    while(1)
    {
    
    
        pthread_mutex_lock(&lock1);       
        printf("线程1抢到lock1\n");
        pthread_mutex_lock(&lock2);       
        printf("线程1抢到lock2\n");
        pthread_mutex_unlock(&lock1);       
        pthread_mutex_unlock(&lock2);       
    }
}

void* func2(void* arg)
{
    
    
    while(1)
    {
    
    
        pthread_mutex_lock(&lock2);       
        printf("线程2抢到lock1\n");
        pthread_mutex_lock(&lock1);       
        printf("线程2抢到lock2\n"); 
        pthread_mutex_unlock(&lock2);       
        pthread_mutex_unlock(&lock1);       
    }
}


int main()
{
    
    
    pthread_t t1, t2;
    pthread_create(&t1, NULL, func1, NULL);
    pthread_create(&t2, NULL, func2, NULL);
    pthread_mutex_init(&lock1, NULL);
    pthread_mutex_init(&lock2, NULL);
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_mutex_destroy(&lock1);
    pthread_mutex_destroy(&lock2);
}

条件変数

ミューテックスとは異なり、条件変数はロックではなく待機に使用され、条件変数自体はロックではありません。
条件変数は、特別な条件が発生するまでスレッドを自動的にブロックするために使用されます。通常、条件変数とミューテックスは同時に使用されます。

条件変数に対する 2 つのアクション:

  • 条件が満たされていないため、スレッドがブロックされています
  • 条件が満たされると、ブロックされたスレッドに作業を再開するように通知します

条件変数関連機能

pthread_cond_init

int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);

機 能:
条件変数の初期化
パラメータ:
cond : 初期化する条件変数へのポインタ。
attr : 条件変数属性、通常はデフォルト値、NULL を渡すだけ
条件変数を初期化するために静的初期化メソッドを使用することもできます:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
戻り値:
成功: 0 失敗: 非 0



pthread_cond_destroy

#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);

機能:
条件変数の破棄
パラメータ:
cond : 初期化する条件変数へのポインタ 戻り値 :
成功: 0 失敗: ゼロ以外



pthread_cond_wait

#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict (cond,
pthread_mutex_t *restrict mutex);

機能:
条件変数待ちブロック
パラメータ :
cond : 初期化する条件変数へのポインタ ミューテックス : ミューテックス
戻り値 :
成功 : 0 失敗 : 0 以外

この関数を呼び出すと、取得したロックが自動的に解放され、起動時にロックが再度保持されます。


pthread_cond_signal

#inelude <pthread.h>
int pthread_cond_signa1(pthread_cond_t *cond);

関数:
条件変数でブロックされている少なくとも 1 つのスレッドをウェイクアップ
パラメータ:
cond : 初期化される条件変数へのポインタ 戻り値:
成功: 0 失敗: 0 以外


int pthread_cond_broadcast(pthread_cond_t *cond);

機能:
条件変数でブロックされているすべてのスレッドをウェイクアップ
パラメータ:
cond : 初期化する条件変数へのポインタ 戻り値 :
成功: 0 失敗: 0 以外


コード例

スレッド B はスレッド A の実行を制御します。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>


pthread_cond_t cond;
pthread_mutex_t lock;
void* func1(void* arg)
{
    
    
    while(1)
    {
    
    
        pthread_cond_wait(&cond, &lock);
        printf("消费了一个\n");
       sleep(1);
    }

}


void* func2(void* arg)
{
    
    
    while(1)
    {
    
    
        pthread_cond_signal(&cond);
        printf("生产了一个\n");
       sleep(1);
    }
}


int main()
{
    
    
    pthread_mutex_init(&lock, NULL);
    pthread_cond_init(&cond, NULL);
    pthread_t t1, t2;
    pthread_create(&t1, NULL, func1, NULL);
    pthread_create(&t2, NULL, func2, NULL)

    pthread_mutex_destroy(&lock);
    pthread_cond_destroy(&cond);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL)
}



生産者消費者モデル

生産者と消費者のモデルでは、コンテナーを使用して、生産者と消費者の間の強い結合の問題を解決します。プロデューサとコンシューマは互いに直接通信しませんが、ブロッキング キューを介して通信します. したがって、データを生成した後、プロデューサはコンシューマがデータを処理するのを待つ必要はありませんが、データをブロッキング キューに直接投げます. コンシューマはプロデューサにデータを要求しませんデータですが、プロデューサーとコンシューマーの処理能力のバランスをとるバッファーに相当するブロッキング キューから直接取得します。このブロッキング キューは、プロデューサーとコンシューマーを分離するために使用されます。


生産者と消費者の関係

プロデューサーとプロデューサーの間には相互に排他的な関係があり、プロデューサーとコンシューマーは同期的であり、コンシューマーとコンシューマーは相互に排他的です。


生産者消費者モデルの利点

  • 生産者と消費者のデカップリングを実現
  • 高い同時実行性


BlockingQueue プロデューサー コンシューマー モデル

ブロッキング キュー (ブロッキング キュー) は、マルチスレッド プログラミングでプロデューサー モデルとコンシューマー モデルを実装するために一般的に使用されるデータ構造です。通常のキューとの違いは、キューが空の場合、要素がキューに入れられるまでキューから要素を取得する操作がブロックされ、キューがいっぱいになると、要素をキューに格納する操作がブロックされることです。また、要素がキューから取り出されるまでブロックされます (上記の操作は異なるスレッドに基づいており、ブロッキング キュー プロセスで操作する場合、スレッドはブロックされます)。

ここに画像の説明を挿入



シミュレーションの実装

BlockQueue.hpp

#include <unistd.h>
#include <pthread.h>
#include <iostream>
#include <queue>
#include <cmath>

class BlockQueue{
    
    

private:
    std::queue<int> _que;
    const size_t _capacity = 5;
    pthread_mutex_t lock;
    pthread_cond_t cond_p;
    pthread_cond_t cond_c;
public:
    
    void Put()
    {
    
    
        while(IsFull())
        {
    
    
            WakeUpConsumer();
            ProducterWait();
        }
        
        int data = 1;
        _que.push(data);        
        std::cout << "product data" << std::endl;
    }

    int Get()
    {
    
    
        while(IsEmpty())
        {
    
    
            WakeupProducter();
            ConsumerWait(); 
        }
        
        int ret = _que.front();
        _que.pop();
        std::cout << "consume data" << std::endl;
        return ret;
    }

    bool IsEmpty()
    {
    
    
        return _que.empty();
    }

    bool IsFull()
    {
    
    
        return _que.size() >= _capacity;
    }
    
    void ConsumerWait()
    {
    
    
        pthread_cond_wait(&cond_c, &lock);
    }
    void ProducterWait()
    {
    
    
        pthread_cond_wait(&cond_p, &lock);
    }
    
    void WakeUpConsumer()
    {
    
    
        pthread_cond_signal(&cond_c);
    }
    
    void WakeupProducter()
    {
    
    
        pthread_cond_signal(&cond_p);
    }
};

main.cpp

#include "pc_model.hpp"

void* product(void* arg)
{
    
    
    BlockQueue* bq = (BlockQueue*)arg;
    while(1)
    {
    
    
        bq->Put();
    }

    return NULL;
}

void* consume(void* arg)
{
    
    
    BlockQueue* bq = (BlockQueue*)arg;
    while(1)
    {
    
    
        std::cout <<  bq->Get() << std::endl;
        usleep(60000);
    }   
    return NULL;
}

int main()
{
    
    
    BlockQueue* bq = new BlockQueue();
    pthread_t t1, t2;
    pthread_create(&t1, NULL, product, (void*)bq);
    pthread_create(&t2, NULL, consume, (void*)bq);

    pthread_exit(nullptr);
}


POSIX セマフォ


POSIX セマフォと SystemV セマフォは同じ機能を持ち、共有リソースへの競合のないアクセスを実現するための同期操作に使用されます。ただし、POSIX はスレッド間同期に使用できます。| |

セマフォを初期化する

#include <semaphore.h>
int sem_init(sem_t *sem,int pshared,unsigned int value) ;
参数:
pshared :0表示线程间共享,非零表示进程间共享
value:信号量初始值

セマフォを破壊する

int sem_destroy(sem_t *sem) ;

セマフォを待つ

功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem) ; //P()

ポストセマフォ

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1int sem_post(em_t *sem) ; //v()

セマフォは基本的にカウンターであり、PV 操作はアトミックです。



サーキュラー キューはプロデューサー コンシューマー モデルを実装します

CircularQueue.hpp

#include <queue>
#include <iostream>
#include <pthread.h>
#include <semaphore.h>
#include <vector>

class CircularQueue {
    
    

private:
    const size_t _capacity;
    sem_t _blank;
    sem_t _data;
    size_t _index_d  = 0;
    size_t _index_b = 0;
    std::vector<int> _v; 
    
public:
    CircularQueue(size_t capacity):
        _capacity(capacity)
    {
    
    
        _v.resize(_capacity); 
        sem_init(&_blank, 0, _capacity); 
        sem_init(&_data, 0, 0);
    }
    ~CircularQueue()
    {
    
    
        sem_destroy(&_blank); 
        sem_destroy(&_data);
    }
        
    void Put(int x)
    {
    
    
        sem_wait(&_blank); 
        
        _v[_index_b++] = x; 
        _index_b %= _capacity; 
        std::cout << "生产了一个" << std::endl;
        sem_post(&_data);
    }

    int Get()
    {
    
     
        sem_wait(&_data); 
        int ret;
        ret = _v[_index_d++];
        _index_d %= _capacity; 

        std::cout << "消费了一个" << std::endl;
        sem_post(&_blank);
      
        return ret;
    }
};

main.cpp

#include "circular_queue.hpp"
#include <unistd.h>

using namespace std;

void* Producter(void* arg)
{
    
    

    CircularQueue* cq = (CircularQueue*)arg;
    int i = 1;
    while(1)
    {
    
    
        cq->Put(i++);
        if(i == 5)
            i = 1;
    }
    
    return NULL;
}

void* Consumer(void* arg)
{
    
    
    CircularQueue* cq = (CircularQueue*)arg;
    while(1)
    {
    
    
        cout << cq->Get() << endl;
        sleep(1);
    }
    
    return NULL;
}
int main()
{
    
    
    pthread_t t1, t2;
    CircularQueue* cq = new CircularQueue(5);
    pthread_create(&t1, NULL, Producter, cq);
    pthread_create(&t2, NULL, Consumer, cq);
    
    pthread_exit(NULL);
}


スレッドプール

おすすめ

転載: blog.csdn.net/juggte/article/details/122645535