[オペレーティング システム - 同期] バンカーのアルゴリズム、マルチスレッドのアトミック操作、POSIX ミューテックス、生産者と消費者の問題を解決するためのセマフォ

ラボ第 14 週の実験レポート

実験内容:プロセス同期

  • 講義 17 のサンプル コードをコンパイルして実行: alg.17-1-bankers-6; 講義 18 のサンプル コード: alg.18-1 ~ alg.18-5 をコンパイルして実行します。不適切と思われる点を指摘し、改善してください。

  • 講義 18: alg.18-6 ~ alg.18-8 のサンプルコードをコンパイルして実行し、生産者と消費者の問題、共有メモリの保護、プロセスの同期メカニズムについて議論し、改善します。

I. バンカーのアルゴリズム

バンカーズ アルゴリズムは、オペレーティング システムがデッドロックを回避するために使用するアルゴリズムです。銀行の融資管理プロセスをモデルにしており、システム リソースを仮に割り当て、セキュリティ チェックを実行して、割り当てが安全かどうか (デッドロックが発生するかどうか) を判断します。割り当てを解除してプロセスを待機させても安全です。

アルゴリズムでは、システムの現在の状態を表すために使用される 3 つの 1 次元配列と 3 つの 2 次元配列を含む 6 つのデータ構造で主に完成されます。プロセス、および M は、割り当て可能なリソースの種類、定義を表します。

int resource[M]int available[M]int request[M]_int max[N][M]int allocation[N][M]int need[N][M]

  • resource[i]i 番目の割り当て可能なリソースのタイプを示します

  • available[i]i 番目のリソースの使用可能量を示します

  • request[i]このプロセスにおける i 番目のリソースのアプリケーションの量を示します

  • max[i][j]プロセス i によるリソース j の最大需要を示します

  • allocation[i][j]プロセス i によって取得されたリソース j の量を示します

  • need[i][j]プロセス i がリソース j のためにまだ必要とするリソースの量を示します

プロセスP i P_iの場合Pシステムにリソースを申請するときは、次のチェックが必要です。

(1) の場合はRequest[i] <= Need[i](2) に進み、そうでない場合はエラーを報告します。

(2)Request[i] <= Avaliable次に (3) に戻る場合、それ以外の場合、プロセスは待機状態に入ります。

(3) 次に、システムがリソースをプロセスに割り当て、新しい状態を生成すると仮定すると、次のようになります。

Avaliale = Avaliable - Request[i];
Allocation[i] = Allocation[i] + Request[i];
Need[i] = Need[i] - Request[i];

また、プロセスの申請プロセスでは、安全なシーケンスを見つけるためにセキュリティ チェックを実行する必要があり、システムの新しい状態が安全な場合は割り当てが完了し、そうでない場合は元の状態に戻す必要があります。となり、プロセスは待機状態になります。

セキュリティ チェックの手順:

データ構造の定義:int work[M]およびbool finish[N]:

  • work[i]i 番目のタイプのリソースの現在の想定可用性を示します ( work[i] = available[i] - request[i])
  • finish[j]j 番目のプロセスがセキュリティ チェックを完了したかどうかを示します

ステップを確認するための疑似コード:

(1) データ構造の初期化

work[i] = avaliable[i]; // 让work为当前资源的可用量
finish[j] = false;  // finish初始化为false,表示未完成安全性检查

(2) ループ判定

a. finish[i] == false; //
b. need[i] <= work;
//若都不满足,转(4)

(3) 資源循環

work = work + allocation; //回收后的资源为可用资源+已分配资源
finish[i] = true; //已检查
//转(2)继续判断其它进程

(4) 最終判断

finish[i] == true //若对所有进程都有该条件成立,则系统处于安全状态
//否则处于不安全状态,因为资源分配给该进程后,没有一个进程能够完成并释放资源,最终将导致死锁。

最後に、{P0、P3、P2、P1} などの安全なシーケンスが返され、システムの現在の残りのリソース作業が示されます。これは、最初にプロセス P0 に割り当てられ、次にリサイクルされます。work+=allocation[p0]

プロセス P3 に再分配され、次にリサイクルされwork += allocation[p3]、以降同様に処理され、最終的にすべてのプロセスが満たされます。

ルーチン コード:

resource_request 関数

int resource_request(int pro_i)
{
    
    
    int j, k, ret;
    
    for (k = 0;  k < M; k++) {
    
    
        if(request[k] > need[pro_i][k]) {
    
    
            printf("request[%d] > need[%d][%d]: request[%d] = %d, need[%d][%d] = %d\n", k, pro_i, k, k, request[k], pro_i, k, need[pro_i][k]);
            return EXIT_FAILURE;
        }
    }

    for (k = 0; k < M; k++) {
    
    
        if(request[k] > available[k]) {
    
    
            printf("request[%d] > available[%d]: request[%d] = %d, available[%d] = %d\n", k, k, k, request[k], k, available[k]);
            return EXIT_FAILURE;
        }
    }

    for (k = 0; k < M; k++) {
    
    
        work[k] = available[k] - request[k]; /* work[] as the pretending available[] */
    }

    ret = safecheck(pro_i); /* check whether the pretending state is safe */

    if(ret == EXIT_SUCCESS) {
    
     /* confirm the pretending state as the new state */
        for (k = 0; k < M; k++) {
    
    
            available[k] -= request[k];
            need[pro_i][k] -= request[k];
            allocation[pro_i][k] += request[k];
        }
    }

    return ret;
}

コードの説明:

コードのこの部分は、リソース割り当て要求の判断を完了し、セキュリティ検出関数を呼び出して要求が安全かどうかを判断します。

1. プロセスのリソース要求がプロセスのリソース要求よりも大きい場合、エラーが返されます。

2. リソースに対するプロセスの要求が、使用可能なリソースの量を超える場合、エラーが返されます。

そのうち、作業配列も初期化され、 work[k] = available[k] - request[k]作業の残りの量 (avaliavle) は、安全検出機能でテストされるように "プローブ" されます. 検出結果が安全である場合、状態が更新され、対応する操作が実行されます:

available[k] -= request[k];
need[pro_i][k] -= request[k];
allocation[pro_i][k] += request[k];

セーフチェック機能

int safecheck(int pro_i)
{
    
    
    int finish[N] = {
    
    FALSE};
    int safe_seq[N] = {
    
    0};
    int i, j, k, l;

    printf("\nsafecheck starting >>>>>>>>\n");

    for (i = 0; i < N; i++) {
    
     /* we need to select N processes, i is just a counter */
        for (j = 0; j < N; j++) {
    
     /* check the j-th process */
            if(finish[j] == FALSE) {
    
    
                if(j == pro_i) {
    
    
                    for (k = 0; k < M; k++) {
    
    
                        if(need[pro_i][k] - request[k] > work[k]) {
    
     /* if pretending need[pro_i] > work[] */
                            break; /* to check next process */
                        }
                    }
                } else {
    
    
                    for (k = 0; k < M; k++) {
    
    
                        if(need[j][k] > work[k]) {
    
    
                            break; /* to check next process */
                        }
                    }
                }

                if(k == M) {
    
     /* the j-th process can finish its task */
                    safe_seq[i] = j;
                    finish[j] = TRUE;
                    printf("safe_seq[%d] = %d\n", i, j);
                    printf("new work vector: ");
                    if(j == pro_i) {
    
    
                        for (l = 0; l < M; l++) {
    
     /* process pro_i releasing pretending allocated resources */
                            work[l] = work[l] + allocation[pro_i][l] + request[l];
                            printf("%d, ", work[l]);
                        }
                    } else {
    
    
                        for (l = 0; l < M; l++) {
    
     /* another process releasing allocated resource */
                            work[l] = work[l] + allocation[j][l];
                            printf("%d, ", work[l]);
                        }
                    }
                    printf("\n");

                    break; /* to select more process */
                }
            }
        }

        if(j == N) {
    
    
            break; /* not enough processes can pass the safecheck */
        }
    }

    if(i == N) {
    
     /* all N processes passed the safecheck */
        printf("A safty sequence is: ");
        for (j = 0; j < N; j++) {
    
    
            printf("P%d, ", safe_seq[j]);
        }
        printf("\n");
        return EXIT_SUCCESS;
    }
    else {
    
    
        printf("safecheck failed, process %d suspended\n", pro_i);
        return EXIT_FAILURE;
    }
}


コードの説明:

コードのこの部分は、銀行家のアルゴリズムのセキュリティ チェックを完了します。

最初に、特定のプロセス Pi のリソースの需要が入力され、配列 に格納されましたrequest[M]。また、work[M]配列もあります。

次に、N のプロセスのセキュリティを確認し、部分的に分析します。

まず、プロセスがセキュリティチェックを完了していない場合、つまりfinish[j] == FALSE次の検出を実行する場合、N のループ判定を実行します。

if(finish[j] == FALSE) {
    
    
  if(j == pro_i) {
    
    
     for (k = 0; k < M; k++) {
    
    
           if(need[pro_i][k] - request[k] > work[k]) {
    
     /* if pretending need[pro_i] > work[] */
              break; /* to check next process */
                              //当前无法满足该进程需求
              }
     }else {
    
    
         for (k = 0; k < M; k++) {
    
    
             if(need[j][k] > work[k]) {
    
     //如果需求量大于剩余量
                              break; /* to check next process */
           }
     }
  }
     if(k == M) {
    
     /* the j-th process can finish its task */
       safe_seq[i] = j; // 加入安全序列中
       finish[j] = TRUE; // 已检查
       printf("safe_seq[%d] = %d\n", i, j);
       printf("new work vector: ");
       if(j == pro_i) {
    
    
         for (l = 0; l < M; l++) {
    
     /* process pro_i releasing pretending allocated resources */
           work[l] = work[l] + allocation[pro_i][l] + request[l];
           printf("%d, ", work[l]);
         }
       } else {
    
    
         for (l = 0; l < M; l++) {
    
     /* another process releasing allocated resource */
           work[l] = work[l] + allocation[j][l];
           printf("%d, ", work[l]);
         }
       }
       printf("\n");

       break; /* to select more process */
     }
    /*.......*/
}

ここで、現在通過しているプロセスがリソースを取得する必要があるプロセスである場合、 とpro_i判断されますneed[pro_i][k] - request[k] > work[k]。つまり、プロセスのリソースの要求量 - 要求量 > 残りのリソース量(ここでは「検出」の量) であれば、現在の残りのリソース量は、プロセスが必要とする作業を完了できず、プロセスのリソースを解放することもできません. 同様に、他のプロセスがプロセスのneed[j][k] > work[k]作業を完了できない場合、これらのプロセスはセキュリティ チェックに合格できません.

逆に、プロセスが作業を完了することができれば、安全であり、セキュリティ シーケンスに追加し、リソースの回復work[l] = work[l] + allocation[pro_i][l] + request[l](プロセス pro_i) またはwork[l] = work[l] + allocation[j][l](他のプロセス) を実行し、作業を更新してから、その後のプロセスを上記のように判断します。

すべてのプロセスがセキュリティ チェックに合格すると、対応するセキュリティ シーケンスが出力されますsafe_seq

操作結果:

初期状態、今度は操作したい工程番号を入力(0を入力)。

画像-20220515154449924

1つ目は初期状態です.現在のシステムには3種類のリソースがあらかじめ設定されています.使用可能なリソースの数は10,5,7です.Pro0〜Pro4の合計5つのプロセスがあります.割り当てられたリソースと需要が与えられます.それぞれ割り当てと必要性によって。

プロセス P0 が必要とするリソースのタイプの数を入力します。それぞれ 3、3、および 3 です。

画像-20220515154857500

現在の残りのシステム リソース数を作業ベクトルとして、5 プロセスのセーフチェック プロセスを出力します。最後に、システムの新しい状態が取得され、残りのリソースはそれぞれ 7、2、および 4 です。

セキュリティ シーケンスは、P0、P1、P2、P3、P4 です。

プロセス P1 を操作して、リソースの量 3、1、2 を取得します。

画像-20220515155318464

このとき、安全チェックの作業ベクトルは残りのシステム リソース 7、2、4 であり、安全シーケンスは P1、P0、P2、P3、P4 です。

プロセス 4 を操作してシステム リソース 4、1、3 を取得する

画像-20220515165700515

この時点で、リソース 3 が不足しており、残りのリソースの量は 2 であり、要求されたリソースの量は 3 です。

プロセス 1 を操作して、システム リソース 1、1、1 を取得します。

画像-20220515165846980

この時点で、リソース要求はプロセスのリソース要求よりも大きくなっています。

プロセス 1 を操作して、システム リソース 0、1、0 を取得します。

画像-20220515170047956

プロセス 1 へのリソースの割り当てに成功しました。

II. プロセスの同期

マルチスレッド プログラムにおける操作の原子性

アトミックおよび非アトミック操作:

1. 関連機能

__sync__ アトミックファミリー

// 将value加到*ptr上,结果更新到*ptr,并返回操作之前*ptr的值
type __sync_fetch_and_add (type *ptr, type value); 

// 从*ptr减去value,结果更新到*ptr,并返回操作之前*ptr的值
type __sync_fetch_and_sub (type *ptr, type value, ...) 

// 将*ptr与value相或,结果更新到*ptr, 并返回操作之前*ptr的值
type __sync_fetch_and_or (type *ptr, type value, ...) 

// 将*ptr与value相与,结果更新到*ptr,并返回操作之前*ptr的值
type __sync_fetch_and_and (type *ptr, type value, ...) 

// 将*ptr与value异或,结果更新到*ptr,并返回操作之前*ptr的值
type __sync_fetch_and_xor (type *ptr, type value, ...) 

// 将*ptr取反后,与value相与,结果更新到*ptr,并返回操作之前*ptr的值
type __sync_fetch_and_nand (type *ptr, type value, ...) 

// 将value加到*ptr上,结果更新到*ptr,并返回操作之后新*ptr的值
type __sync_add_and_fetch (type *ptr, type value, ...) 

// 从*ptr减去value,结果更新到*ptr,并返回操作之后新*ptr的值
type __sync_sub_and_fetch (type *ptr, type value, ...) 

// 将*ptr与value相或, 结果更新到*ptr,并返回操作之后新*ptr的值
type __sync_or_and_fetch (type *ptr, type value, ...) 

// 将*ptr与value相与,结果更新到*ptr,并返回操作之后新*ptr的值
type __sync_and_and_fetch (type *ptr, type value, ...) 

// 将*ptr与value异或,结果更新到*ptr,并返回操作之后新*ptr的值
type __sync_xor_and_fetch (type *ptr, type value, ...)

// 将*ptr取反后,与value相与,结果更新到*ptr,并返回操作之后新*ptr的值
type __sync_nand_and_fetch (type *ptr, type value, ...) 

// 比较*ptr与oldval的值,如果两者相等,则将newval更新到*ptr并返回true
bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)

// 比较*ptr与oldval的值,如果两者相等,则将newval更新到*ptr并返回操作之前*ptr的值
type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...) 

// 发出完整内存栅栏
__sync_synchronize (...) 

// 将value写入ptr,对ptr加锁,并返回操作之前ptr的值。
type __sync_lock_test_and_set (type ptr, type value, ...)

// 将0写入到ptr,并对*ptr解锁。
void __sync_lock_release (type ptr, ...)


2. ルーチン コードが実行されます。

同期されたアトミック操作

alg.18-1-syn-fetch-demo.c

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

#define MAX_N 40

static int count_1 = 0;
static int count_2 = 0;

void *thread_func1(void *arg)
{
    
    

    for (int i = 0; i < 20000; ++i) {
    
    
        __sync_fetch_and_add(&count_1, 1);
    }

    pthread_exit(NULL);
}

void *thread_func2(void *arg)
{
    
    
    for (int i = 0; i < 20000; ++i) {
    
    
        count_2++;
    }

    pthread_exit(NULL);
}

int main(void)
{
    
    
    pthread_t ptid_1[MAX_N];
    pthread_t ptid_2[MAX_N];

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_create(&ptid_1[i], NULL, &thread_func1, NULL);
    }

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_create(&ptid_2[i], NULL, &thread_func2, NULL);
    }

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_join(ptid_1[i], NULL);
    }

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_join(ptid_2[i], NULL);
    }

    printf("result conut_1 = %d\n", count_1);
    printf("result conut_2 = %d\n", count_2);

    return 0;
}


コードの説明:

関数ではthread_func1()、20,000 ループを実行し、 __sync_fetch_and_add(&count_1, 1)アトミック操作が呼び出されるたびに count_1 に 1 を追加します。

この関数ではthread_func2()、アトミック操作ではない count_2++ 操作のみが実行されます. 対応するアセンブリ コードから、 ** この操作は 3 つのアトミック ステップに分割する必要があります。 1 を加算し、最後に値をメモリに書き戻す **このプロセスでは、スレッド間の競合が発生しやすく、誤った結果が生成されます。

運用実績

画像-20220516111359234

thread_func1()明らかに、関数ではアトミック操作を呼び出して実行された操作の結果は正しいが、thread_func2()関数では得られた結果が間違っていることが結果からわかります。

alg.18-1-syn-fetch.c

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main(void)
{
    
    
    uint8_t i, val;
    
    i = 30; val = 10;
	printf("__sync_fetch_and_add(&i, val)\n");
	printf("old i = %d, val = %d, ", i, val);
	printf("ret = %d, ", __sync_fetch_and_add(&i, val));
    printf("new i = %d\n\n", i);
    
    i = 30; val = 10;
	printf("__sync_add_and_fetch(&i, val)\n");
	printf("old i = %d, val = %d, ", i, val);
	printf("ret = %d, ", __sync_add_and_fetch(&i, val));
    printf("new i = %d\n\n", i);

    i = 30; val = 10;
	printf("__sync_fetch_and_sub(&i, val)\n");
	printf("old i = %d, val = %d, ", i, val);
	printf("ret = %d, ", __sync_fetch_and_sub(&i, val));
    printf("new i = %d\n\n", i);
    
    i = 0x16; val = 0x2A;
	printf("__sync_fetch_and_or(&i, val)\n"); /* 00010110 | 00101010 */
    printf("old i = 0x%x, val = 0x%x, ", i, val);
	printf("ret = 0x%x, ", __sync_fetch_and_or(&i, val));
    printf("new i = 0x%x\n\n", i);
    
    i = 0x16; val = 0x2A;
	printf("__sync_fetch_and_and(&i, val)\n"); /* 00010110 & 00101010 */
    printf("old i = 0x%x, val = 0x%x, ", i, val);
	printf("ret = 0x%x, ", __sync_fetch_and_and(&i, val));
    printf("new i = 0x%x\n\n", i);
    
    i = 0x16; val = 0x2A;
	printf("__sync_fetch_and_xor(&i, val)\n"); /* 00010110 ^ 00101010 */
    printf("old i = 0x%x, val = 0x%x, ", i, val);
	printf("ret = 0x%x, ", __sync_fetch_and_xor(&i, val));
    printf("new i = 0x%x\n\n", i);
    
    i = 0x16; val = 0x2A;
	printf("__sync_fetch_and_nand(&i, val)\n"); /* ~(00010110 & 00101010) */
    printf("old i = 0x%x, val = 0x%x, ", i, val);
	printf("ret = 0x%x, ", __sync_fetch_and_nand(&i, val));
    printf("new i = 0x%x\n\n", i);
    
	return 0;
}



コードの説明:

コードのこの部分では、__sync__ ファミリのアトミック操作が呼び出され、計算結果が出力されます。

__sync_fetch_and_add(&i, val)i の値を取り出して val に相当する値を加算し、最後に i が変化しない前の値を返すのですが、逆に、i に__sync_add_and_fetch(&i, val)val に相当する値を加算してから、最後に を返します。 i が変更された後の値。

運用実績画像-20220518124948283

画像-20220516111540749

__sync_fetch_and_add(&i, val)実行後、最初に i の値 30 を取り出し、val 10 に対応する値を追加し、最後に i が変化しない前に値 30 を返し、最後に i 40 の値を出力するという実装であることがわかります; そして、最初に put i __sync_add_and_fetch(&i, val)= val に対応する値 10 に 30 を加算し、最後に変更後の i の値を 40 に戻します。

alg.18-2-syn-compare.c

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main(void)
{
    
    
    uint8_t i, oldval, newval;
    
    i = 30; oldval = 30; newval = 40;
    printf("__sync_bool_compare_and_swap(&i, oldval, newval)\n");
    printf("i = %d, oldval = %d, newval = %d\n", i, oldval, newval);
    printf("ret = %d, ", __sync_bool_compare_and_swap(&i, oldval, newval));
    printf("new i = %d\n", i);
    i = 30; oldval = 10; newval = 40;
    printf("__sync_bool_compare_and_swap(&i, oldval, newval)\n");
    printf("i = %d, oldval = %d, newval = %d\n", i, oldval, newval);
    printf("ret = %d, ", __sync_bool_compare_and_swap(&i, oldval, newval));
    printf("new i = %d\n\n", i);
 
    i = 30; oldval = 30; newval = 40;
    printf("__sync_val_compare_and_swap(&i, oldval, newval)\n");
    printf("i = %d, oldval = %d, newval = %d\n", i, oldval, newval);
    printf("ret = %d, ", __sync_val_compare_and_swap(&i, oldval, newval));
    printf("new i = %d\n", i);
    i = 30; oldval = 10; newval = 40;
    printf("__sync_val_compare_and_swap(&i, oldval, newval)\n");
    printf("i = %d, oldval = %d, newval = %d\n", i, oldval, newval);
    printf("ret = %d, ", __sync_val_compare_and_swap(&i, oldval, newval));
    printf("new i = %d\n\n", i);

    i = 30; newval = 10;
    printf("__sync_lock_test_and_set(&i, newval)\n");
    printf("i = %d, newval = %d\n", i, newval);
    printf("ret = %d, ", __sync_lock_test_and_set(&i, newval));
    printf("new i = %d\n", i);

    i = 30;
    printf("__sync_lock_release(&i)\n");
    printf("i = %d\n", i);
    __sync_lock_release(&i); /* no return value */
    printf("new i = %d\n", i);

    return 0;
}


コードの説明:

__sync_bool_compare_and_swap(&i, oldval, newval)アトミックな比較と交換では、i の値が oldval の値と同じである場合、newval の値を i に代入し、True を返します。それ以外の場合は False を返します。

__sync_val_compare_and_swap(&i, oldval, newval)原理は上記の関数と同じですが、戻り値は変更前の i の値になります。

__sync_lock_test_and_set(&i, newval)newval は i に書き込まれ、i をロックし、操作前の i の値を返します。

__sync_lock_release(&i)i に 0 を書き込み、ロックを解除します。

運用実績

画像-20220516111851981

__sync_bool_compare_and_swap(&i, oldval, newval)結果から、最初の呼び出しの i の値が oldval の値と同じであることがわかります。その後、newval の値 40 が i に割り当てられるため、最終的には i = 40 となり、その逆になります。したがって、newval の値は代入され

最初の呼び出し __sync_val_compare_and_swap(&i, oldval, newval)の i の値は oldval の値と同じで、newval の値 40 が i に割り当てられるため、最終的に i = 40; 2 番目の呼び出しは反対であるため、newval の値は変更されません。関数の戻り値は、変更前の i の値とまったく同じです。

__sync_lock_test_and_set(&i, newval)i に newval を書き込み、i をロックします。new_i = 10 であることに注意し、演算前に i の値 30 を返します。

__sync_lock_release(&i)i に 0 を書き込み、ロックを解除します。new_i = 0; であることに注意してください。

alg.18-3-syn-pthread-mutex.c

POSIXミューテックス

Mutex (相互排他ロック) は、スリープ待機タイプのロックに属しますたとえば、デュアルコア マシンには 2 つのスレッド (スレッド A とスレッド B) があり、それぞれ Core0 と Core1 で実行されます。スレッド A が pthread_mutex_lock 操作を通じてクリティカル セクションのロックを取得しようとしており、このロックがこの時点でスレッド B によって保持されていると仮定すると、スレッド A はブロックされ、Core0 はこの時点でコンテキストの切り替え (Context Switch) を実行します。スレッド A は待機キューに入れられ、コア 0 はビジー待機なしで他のタスクを実行できます。

ミューテックスのコンテキスト切り替えにはかなりの時間がかかる可能性があり、ロックが非常に短時間しか使用されない場合、コンテキスト切り替えはそれに比べて膨大なオーバーヘッドになります。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
//#include <string.h>

#define MAX_N 40

static int count_1 = 0;
static int count_2 = 0;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    /* or declared: 
       pthread_mutex_t mutex;
       and in main():
       pthread_mutex_init(&mutex, NULL); */

void *thread_func1(void *arg)
{
    
    
    for (int i = 0; i < 20000; ++i) {
    
    
        pthread_mutex_lock(&mutex);
        count_1++;
        pthread_mutex_unlock(&mutex);
    }

    pthread_exit(NULL);
}

void *thread_func2(void *arg)
{
    
    
    for (int i = 0; i < 20000; ++i) {
    
    
        count_2++;
    }

    pthread_exit(NULL);
}

int main(void)
{
    
    
    pthread_t ptid_1[MAX_N];
    pthread_t ptid_2[MAX_N];

//    pthread_mutex_init(&mutex, NULL);

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_create(&ptid_1[i], NULL, &thread_func1, NULL);
    }

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_create(&ptid_2[i], NULL, &thread_func2, NULL);
    }

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_join(ptid_1[i], NULL);
    }

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_join(ptid_2[i], NULL);
    }

    pthread_mutex_destroy(&mutex);

    printf("result count_1 = %d\n", count_1);
    printf("result count_2 = %d\n", count_2);

    return 0;
}


コードの説明:

ミューテックスはpthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER静的に初期化されます。PTHREAD_MUTEX_INITIALIZER は構造体定数であるか、pthread_mutex_init(&mutex, NULL)動的初期化に使用されます。

thread_func1 関数では、mutex を使用してクリティカル セクションの競合の問題を解決しています。

void *thread_func1(void *arg)
{
    
    
    for (int i = 0; i < 20000; ++i) {
    
    
        pthread_mutex_lock(&mutex);
        count_1++;
        pthread_mutex_unlock(&mutex);
    }

    pthread_exit(NULL);
}

20,000 回の for ループでは、pthread_mutex_lock(&mutex)ループごとにロックを取得し、count_1 を 1 ずつ増やしてから pthread_mutex_unlock(&mutex)ロックを解放しますが、この過程でスレッド間の競合は発生しません。

thread_fun2 関数では、mutex メカニズムは使用されず、関数を呼び出すときにスレッドが競合します。

最後に、pthread_mutex_destroy(&mutex)ミューテックスを解放するために呼び出されます。

運用実績

画像-20220516113317304

この結果から、thread_func1 関数の count_1 の結果は正しいのですが、thread_func2 関数の count_2 に競合があり、結果が正しくありません。

POSIX セマフォ
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);

1. 名前付きセマフォ: プロセス間で共有できます (通常はプロセス間通信に使用される名前付きパイプに似ています)

alg.18-4-syn-pthread-sem-named.c

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <fcntl.h>

#define MAX_N 40

sem_t *named_sem; /* global long int pointer */

static int count_1 = 0;
static int count_2 = 0;

void *thread_func1(void *arg)
{
    
    
    for (int i = 0; i < 20000; ++i) {
    
    
        sem_wait(named_sem);
        count_1++;
        sem_post(named_sem);
    }

    pthread_exit(NULL);
}

void *thread_func2(void *arg)
{
    
    
    for (int i = 0; i < 20000; ++i) {
    
    
        count_2++;
    }

    pthread_exit(NULL);
}

int main(void)
{
    
    
    pthread_t ptid_1[MAX_N];
    pthread_t ptid_2[MAX_N];

    int i, ret;

    named_sem = sem_open("MYSEM", O_CREAT, 0666, 1);
        /* a file named "sem.MYSEM" is created in /dev/shm/ 
           to be shared by processes who can sense the file name */

    if(named_sem == SEM_FAILED) {
    
    
        perror("sem_open()");
        return EXIT_FAILURE;
    }

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_create(&ptid_1[i], NULL, &thread_func1, NULL);
    }

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_create(&ptid_2[i], NULL, &thread_func2, NULL);
    }

    for (i = 0; i < MAX_N; ++i) {
    
    
        pthread_join(ptid_1[i], NULL);
    }

    for (i = 0; i < MAX_N; ++i) {
    
    
        pthread_join(ptid_2[i], NULL);
    }

    printf("result count_1 = %d\n", count_1);
    printf("result count_2 = %d\n", count_2);

    sem_close(named_sem);
    sem_unlink("MYSEM"); /* remove sem.MYSEM from /dev/shm/ when its references is 0 */

    return 0;
}


コードの説明:

この関数は、sem_open()新しいセマフォを作成するか、既存のセマフォを開くために使用されます。named_sem = sem_open("MYSEM", O_CREAT, 0666, 1)「MYSEM」という名前のセマフォが作成され、初期値は 1 で、戻り値はセマフォ記述子です。

thread_func1()関数内にsem_wait(named_sem)も20,000回のループがあり、セマフォを取得するために呼び出されるたびに、セマフォを1ずつデクリメント(P操作)し、クリティカルセクションに入ってcount_1++を実行し、最後に呼び出してsem_post(named_sem)セマフォを解放し、セマフォが 1 加算されます (V 演算)

sem_close(named_sem)セマフォを閉じるために最後に実行されます。

運用実績

画像-20220516164117597

この結果から、thread_func1 関数でセマフォを使用して実行された自己インクリメント操作の count_1 の結果は正しいですが、thread_func2 関数の count_2 は矛盾しており、結果は正しくありません。

2. 匿名セマフォ: (通常はスレッド間通信に使用)

int sem_init(sem_t *sem, int pshared, unsigned int value)
// 参数为: 1)信号量的指针 2)表示共享级别的标志 3)信号量的初始值
//pshared = 0表示此信号量只能由属于创建该信号量的同一进程的线程共享。
  

alg.18-5-syn-pthread-sem-unnamed.c

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

#define MAX_N 40

sem_t unnamed_sem; /* global long int */

static int count_1 = 0;
static int count_2 = 0;

void *thread_func1(void *arg)
{
    
    
    for (int i = 0; i < 20000; ++i) {
    
    
        sem_wait(&unnamed_sem);
        count_1++;
        sem_post(&unnamed_sem);
    }

    pthread_exit(NULL);
}

void *thread_func2(void *arg)
{
    
    
    for (int i = 0; i < 20000; ++i) {
    
    
        count_2++;
    }

    pthread_exit(NULL);
}

int main(void)
{
    
    
    pthread_t ptid_1[MAX_N];
    pthread_t ptid_2[MAX_N];

    int i, ret;

    ret = sem_init(&unnamed_sem, 0, 1);
    if(ret == -1) {
    
    
        perror("sem_init()");
        return EXIT_FAILURE;
    }
    
    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_create(&ptid_1[i], NULL, &thread_func1, NULL);
    }

    for (int i = 0; i < MAX_N; ++i) {
    
    
        pthread_create(&ptid_2[i], NULL, &thread_func2, NULL);
    }

    for (i = 0; i < MAX_N; ++i) {
    
    
        pthread_join(ptid_1[i], NULL);
    }

    for (i = 0; i < MAX_N; ++i) {
    
    
        pthread_join(ptid_2[i], NULL);
    }

    printf("result count_1 = %d\n", count_1);
    printf("result count_2 = %d\n", count_2);

    sem_destroy(&unnamed_sem);

    return 0;
}


コードの説明:

関数はsem_init()セマフォを初期化するために使用され、セマフォ識別子のアドレスがsem_t unnamed_sem渡されます。 ret = sem_init(&unnamed_sem, 0, 1)初期値が 1 のセマフォが作成され、このセマフォはプロセスにのみ属します。

thread_func1()関数内にsem_wait(named_sem)も20,000回のループがあり、セマフォを取得するために呼び出されるたびに、セマフォを1ずつデクリメント(P操作)し、クリティカルセクションに入ってcount_1++を実行し、最後に呼び出してsem_post(named_sem)セマフォを解放し、セマフォが 1 加算されます (V 演算)

sem_destroy(named_sem)セマフォを破棄するために最後に実行されます。

運用実績

画像-20220516171152795

この結果から、thread_func1 関数でセマフォを使用して実行された自己インクリメント操作の count_1 の結果は正しいですが、thread_func2 関数の count_2 は矛盾しており、結果は正しくありません。

生産者と消費者の問題

alg.18-6-syn-pc-con-6.c

/*  compiling with -pthread
    
    this version works properly
	file list:  syn-pc-con-7.h
		        syn-pc-con-7.c
		        syn-pc-producer-7.c
		        syn-pc-consumer-7.c
    with process shared memory and semaphores
    BUFFER_SIZE, MAX_ITEM_NUM, THREAD_PROD and THREAD_CONS got from input
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <wait.h>
#include "alg.18-6-syn-pc-con-7.h"

int shmid;
void *shm = NULL;
int detachshm(void);
int random_code(unsigned int);

int main(void)
{
    
    
    int buffer_size, max_item_num, thread_prod, thread_cons;
    char code_str[10], pathname[80];
    int fd;
    key_t key;
    int ret;
    struct ctl_pc_st *ctl_ptr = NULL;
    struct data_pc_st *data_ptr = NULL;
    pid_t childpid, prod_pid, cons_pid;
		
    while (1) {
    
     /* env. initialization */
        printf("Pls input the number of items as the buffer bound(1-100, 0 quit): ");
        scanf("%d", &buffer_size);
        if(buffer_size <= 0)
            return 0;
        if(buffer_size > 100)
            continue;
        printf("Pls input the max number of items to be produced(1-10000, 0 quit): ");
        scanf("%d", &max_item_num);
        if(max_item_num <= 0)
            return 0;
        if(max_item_num > 10000)
            continue;
        printf("Pls input the number of producers(1-500, 0 quit): ");
        scanf("%d", &thread_prod);
        if(thread_prod <= 0)
            return 0;
        printf("Pls input the number of consumers(1-500, 0 quit): ");
        scanf("%d", &thread_cons);
        if(thread_cons <= 0)
            return 0;

        break;
    }
    
    sprintf(code_str, "%d", random_code(4));
    strcpy(pathname, "/tmp/shm-");
    strcat(pathname, code_str);
    printf("shm pathname: %s\n", pathname);
    fd = open(pathname, O_CREAT);
	if(fd == -1) {
    
    
        perror("pathname fopen()");
        return EXIT_FAILURE;
    }

    if((key = ftok(pathname, 0x28)) < 0) {
    
     
        perror("ftok()");
        exit(EXIT_FAILURE);
    }
    
      /* get the shared memoey
         'invalid argument' if the size exceeds the current shmmax.
	     when the shmid exists, its size is defined when it was firstly declared and can not be changed.
	     if you want a lager size, you have to alter a new key for a new shmid.
      */
    shmid = shmget((key_t)key, (buffer_size + BASE_ADDR)*sizeof(struct data_pc_st), 0666 | IPC_CREAT);
    if(shmid == -1) {
    
    
        perror("shmget()");
        exit(EXIT_FAILURE);
    }

      /* attach the created shared memory to user space */
    shm = shmat(shmid, 0, 0);
    if(shm == (void *)-1) {
    
    
        perror("shmat()");
        exit(EXIT_FAILURE);
    }

      /* set the shared memory, initialize all control parameters */
    ctl_ptr = (struct ctl_pc_st *)shm;
    data_ptr = (struct data_pc_st *)shm;

    ctl_ptr->BUFFER_SIZE = buffer_size;
    ctl_ptr->MAX_ITEM_NUM = max_item_num;
    ctl_ptr->THREAD_PROD = thread_prod;
    ctl_ptr->THREAD_CONS = thread_cons; 
    ctl_ptr->prod_num = 0;
    ctl_ptr->cons_num = 0;
    ctl_ptr->enqueue = 0;
    ctl_ptr->dequeue = 0;
    ctl_ptr->END_FLAG = 0;

    ret = sem_init(&ctl_ptr->sem_mutex, 1, 1); /* pshared set to non-zero for inter process sharing */
    if(ret == -1) {
    
    
        perror("sem_init-mutex");
        return detachshm();
    }
    ret = sem_init(&ctl_ptr->sem_stock, 1, 0); /* initialize to 0 */
    if(ret == -1) {
    
    
        perror("sem_init-stock");
        return detachshm();
    }
    ret = sem_init(&ctl_ptr->sem_emptyslot, 1, ctl_ptr->BUFFER_SIZE); /*initialize to BUFFER_SIZE */
    if(ret == -1) {
    
    
        perror("sem_init-emptyslot");
        return detachshm();
    }

    printf("\nsyn-pc-con console pid = %d\n", getpid());

    char *argv1[3];
    char execname[] = "./";
    char shmidstring[10];
    sprintf(shmidstring, "%d", shmid);
    argv1[0] = execname;
    argv1[1] = shmidstring;
    argv1[2] = NULL;
        
    childpid = vfork();
    if(childpid < 0) {
    
    
        perror("first fork");
        detachshm();
        unlink(pathname);
        return 0;
    } 
    else if(childpid == 0) {
    
     /* call the producer */ 
        prod_pid = getpid();
        printf("producer pid = %d, shmid = %s\n", prod_pid, argv1[1]);
        execv("./alg.18-7-syn-pc-producer-7.out", argv1);
    }
    else {
    
    
        childpid = vfork();
        if(childpid < 0) {
    
    
            perror("second vfork");
            detachshm();
            unlink(pathname);
            return 0;
        } 
        else if(childpid == 0) {
    
     /* call the consumer */
            cons_pid = getpid();
            printf("consumer pid = %d, shmid = %s\n", cons_pid, argv1[1]);
            execv("./alg.18-8-syn-pc-consumer-7.out", argv1);
        }
    }

    if(waitpid(prod_pid, 0, 0) != prod_pid) {
    
    /* block wait */
        perror("wait prod");
    } else {
    
    
        printf("waiting prod_pid %d success.\n", prod_pid);
    }

    if (waitpid(cons_pid, 0, 0) != cons_pid) {
    
    
        perror("wait cons");
    } else {
    
    
        printf("waiting cons_pid %d success.\n", cons_pid);
    }

    ret = sem_destroy(&ctl_ptr->sem_mutex);
    if(ret == -1) {
    
    
        perror("sem_destroy sem_mutex");
    }
    ret = sem_destroy(&ctl_ptr->sem_stock); /* sem_destroy() will not affect the sem_wait() calling process */
    if(ret == -1) {
    
    
        perror("sem_destroy stock");
    }
    ret = sem_destroy(&ctl_ptr->sem_emptyslot);
    if(ret == -1) {
    
    
        perror("sem_destroy empty_slot");
    }

    detachshm();
    unlink(pathname);
    return EXIT_SUCCESS;
}

int detachshm(void)
{
    
    
    if(shmdt(shm) == -1) {
    
    
        perror("shmdt()");
        exit(EXIT_FAILURE);
    }

    if(shmctl(shmid, IPC_RMID, 0) == -1) {
    
    
        perror("shmctl(IPC_RMID)");
        exit(EXIT_FAILURE);
    }

    return EXIT_SUCCESS;
}

int random_code(unsigned int code_len)
{
    
    
	int code_val;
	long int modulus = 1;
	
	for (int i = 0; i < code_len; i++) {
    
    
		modulus = modulus * 10;
	}
	
	srand(time(NULL));
    while (1) {
    
    
        code_val = rand() % modulus;
        if(code_val > modulus / 10 - 1) {
    
    
            break;
        }
    }
    
	return code_val;
}


コードの説明:

キュー操作: (エンキュー | デキュー) % buffer_size + BASE_ADDR

まず、ユーザーはバッファ サイズ、生産されるアイテムの数、生産者の数、消費者の数を入力する必要があります。

次に、 shmid = shmget((key_t)key, (buffer_size + BASE_ADDR)*sizeof(struct data_pc_st), 0666 | IPC_CREAT)以前にランダムに生成されたファイル名に従って取得されたキーを呼び出して、サイズが(buffer_size + BASE_ADDR)*sizeof(struct data_pc_st);の共有メモリ空間を作成します。

再度呼び出して shm = shmat(shmid, 0, 0)、共有メモリ領域を呼び出しプロセスのアドレス空間にマップし、プロセスがそれにアクセスできるようにします。

次に、共有メモリを設定します, それぞれ制御構造とデータ構造です. これらの2つの構造は、後でvforkから出てくる子プロセスと共有されます:

ctln = (struct ctln_pc_st *)shm;
data = (struct data_pc_st *)shm;

次に、制御構造内のミューテックス セマフォ、バッファ ストレージ量セマフォ、およびバッファ セマフォ内の空きユニット数を初期化します。

ret = sem_init(&ctln->sem_mutex, 1, 1)
ret = sem_init(&ctln->stock, 1, 0);
ret = sem_init(&ctln->emptyslot, 1, ctln->BUFFER_SIZE);

pshare はミューテックス セマフォ (ctln->sem_mutex) の初期化で (0 ではなく) 1 として指定されていることに注意してください。これは、これがプロセス間共有に使用され、1 に初期化されることを示しています。

バッファストレージ量のセマフォ (ctln->stock) も pshare が 1 で、0 に初期化されます。

バッファ内の空きユニット数を示すセマフォ (ctln->emptyslot) は BUFFER_SIZE に初期化されます

初期化作業が完了すると、vfork() によってプロセスが作成され、プロデューサー プログラムとコンシューマー プログラムの呼び出しexecv()に使用されます execv("./alg.18-8-syn-pc-consumer-7.out", argv1)。つまり、 execv("./alg.18-7-syn-pc-producer-7.out", argv1)ここでの argv1 パラメーターは shmid です。

プロデューサー:

alg.18-7-syn-pc-producer-7.c

/*  compiling with -pthread

    this version works properly
    file list:  syn-pc-con-7.h
                syn-pc-con-7.c
                syn-pc-producer-7.c
                syn-pc-consumer-7.c
    with process shared memory and semaphores
*/

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/syscall.h>
#include "alg.18-6-syn-pc-con-7.h"
#define gettid() syscall(__NR_gettid)

struct ctl_pc_st *ctl_ptr = NULL;
struct data_pc_st *data_ptr = NULL;

static int item_sn;
int random_code(unsigned int);

void *producer(void *arg)
{
    
    
  
  //当已经生产的产品数量小于所要生产的产品数量时,进入生产循环
    while (ctl_ptr->prod_num < ctl_ptr->MAX_ITEM_NUM) {
    
    
        //这里的sem_emptyslot在此前被初始化为BUFFER_SIZE
        sem_wait(&ctl_ptr->sem_emptyslot); //若缓冲区的空闲单元信号量大于0,则可以存放产品,这里将该信号量减1,否则需要等待
        sem_wait(&ctl_ptr->sem_mutex); //若互斥信号量小于1,则需要等待,这里将该信号量减1
        if(ctl_ptr->prod_num < ctl_ptr->MAX_ITEM_NUM) {
    
    
            ctl_ptr->prod_num++;	//产品数加1
            (data_ptr + ctl_ptr->enqueue + BASE_ADDR)->prod_tid = gettid(); // 将产品的线程号和序列号入队
            (data_ptr + ctl_ptr->enqueue + BASE_ADDR)->item_sn = item_sn++;
            printf("producer tid %ld prepared item_sn %d, now enqueue = %d\n", (data_ptr + ctl_ptr->enqueue + BASE_ADDR)->prod_tid, (data_ptr + ctl_ptr->enqueue + BASE_ADDR)->item_sn, ctl_ptr->enqueue);
            ctl_ptr->enqueue = (ctl_ptr->enqueue + 1) % ctl_ptr->BUFFER_SIZE;
          //当已经生产的产品数量等于所要生产的产品数量时,将结束标志END_FLAG置为1
            if(ctl_ptr->prod_num == ctl_ptr->MAX_ITEM_NUM) {
    
     
                ctl_ptr->END_FLAG = 1;
		    }
            sem_post(&ctl_ptr->sem_stock);  //将缓冲区存储数量的信号量加1
        } else {
    
     //当已经生产的产品数量不小于所要生产的产品数量时,将表示缓冲区的空闲单元信号量加1
            sem_post(&ctl_ptr->sem_emptyslot); 
        }
        sem_post(&ctl_ptr->sem_mutex); // 释放互斥信号量,允许其它线程访问临界区
        usleep(random_code(6));
    }

    pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
    
    
    int shmid;
    void *shm = NULL;
    int i, ret;

    shmid = strtol(argv[1], NULL, 10); /* shmid delivered */
    shm = shmat(shmid, 0, 0);
    if(shm == (void *)-1) {
    
    
        perror("\nproducer shmat()");
        exit(EXIT_FAILURE);
    }

    ctl_ptr = (struct ctl_pc_st *)shm;
    data_ptr = (struct data_pc_st *)shm;

    pthread_t ptid[ctl_ptr->THREAD_PROD];

    item_sn = random_code(8);

    for (i = 0; i < ctl_ptr->THREAD_PROD; ++i) {
    
    
        ret = pthread_create(&ptid[i], NULL, &producer, NULL);
        if(ret != 0) {
    
    
            perror("producer pthread_create()");
            break;
        }
    }    

    for (i = 0; i < ctl_ptr->THREAD_PROD; ++i) {
    
    
        pthread_join(ptid[i], NULL);
    }

    for (i = 0; i < ctl_ptr->THREAD_CONS - 1; ++i) {
    
    
      /* all producers stop working, in case some consumer takes the last stock
         and no more than THREAD_CON-1 consumers stick in the sem_wait(&stock) */
        sem_post(&ctl_ptr->sem_stock);
    }
    
    if(shmdt(shm) == -1) {
    
    
        perror("producer shmdt()");
        exit(EXIT_FAILURE);
    }

    exit(EXIT_SUCCESS);
}

int random_code(unsigned int code_len)
{
    
    
	int code_val;
	long int modulus = 1;
	
	for (int i = 0; i < code_len; i++) {
    
    
		modulus = modulus * 10;
	}
	
	srand(time(NULL));
    while (1) {
    
    
        code_val = rand() % modulus;
        if(code_val > modulus / 10 - 1) {
    
    
            break;
        }
    }
    
	return code_val;
}

コードの説明:

コードのこの部分は、THREAD_PROD プロデューサ スレッドの非同期生産プログラムに対応します. 条件がctl_ptr->prod_num < ctl_ptr->MAX_ITEM_NUM満たされた場合、つまり、既に生産された製品の数が生産される製品の数よりも少ない場合、生産コードは循環的に呼び出されます。そして、対応する製品が循環キューに挿入されctl_ptr->prod_num == ctl_ptr->MAX_ITEM_NUM、製品の生産量が製品の生産量と等しくなったとき、生産は終了します。

具体的な詳細は、コードのコメントに記載されています。

消費者:

alg.18-8-syn-pc-consumer-7

/*  compiling with -pthread

    this version works properly
    file list:  syn-pc-con-7.h
                syn-pc-con-7.c
                syn-pc-producer-7.c
                syn-pc-consumer-7.c
    with process shared memory and semaphores 
*/

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/syscall.h>
#include "alg.18-6-syn-pc-con-7.h"
#define gettid() syscall(__NR_gettid)

struct ctl_pc_st *ctl_ptr = NULL;
struct data_pc_st *data_ptr = NULL;

int random_code(unsigned int);

void *consumer(void *arg)
{
    
    
   //当消费者已经消费的产品数小于生产者生产的产品数或者生产者还没生产结束时
    while ((ctl_ptr->cons_num < ctl_ptr->prod_num) || (ctl_ptr->END_FLAG == 0))  {
    
     
      //当缓冲区产品存储数量的信号量大于0时,表示当中有产品可以消费,将该信号量减1
        sem_wait(&ctl_ptr->sem_stock);  /* if stock is empty and all producers stop working at this point, one or more consumers may wait forever */
      //若互斥信号量不小于1,则进入临界区,否则需要等待。
        sem_wait(&ctl_ptr->sem_mutex);
      //当消费者已经消费的产品数小于生产者生产的产品数
        if (ctl_ptr->cons_num < ctl_ptr->prod_num) {
    
     
            printf("\t\t\t\tconsumer tid %ld taken item_sn %d by tid %ld, now dequeue = %d\n", gettid(), (data_ptr + ctl_ptr->dequeue + BASE_ADDR)->item_sn, (data_ptr + ctl_ptr->dequeue + BASE_ADDR)->prod_tid, ctl_ptr->dequeue);
           //将产品出队
            ctl_ptr->dequeue = (ctl_ptr->dequeue + 1) % ctl_ptr->BUFFER_SIZE;
          //消费的产品数加1
            ctl_ptr->cons_num++;
          //将表示缓冲区空闲单元的信号量加1
            sem_post(&ctl_ptr->sem_emptyslot);
          
          // 当消费者已经消费的项目数量不小于生产者已经生产的项目数量,将表示缓冲区中存储数量的信号量加1
        } else {
    
    
            sem_post(&ctl_ptr->sem_stock);
        }
      //释放互斥信号量
        sem_post(&ctl_ptr->sem_mutex);
        usleep(random_code(6));
    }
    
    pthread_exit(0);
}

int main(int argc, char *argv[])
{
    
    
    int shmid;
    void *shm = NULL;
    int i, ret;

    shmid = strtol(argv[1], NULL, 10); /* shmnid delivered */
    shm = shmat(shmid, 0, 0);
    if (shm == (void *)-1) {
    
    
        perror("consumer shmat()");
        exit(EXIT_FAILURE);
    }

    ctl_ptr = (struct ctl_pc_st *)shm;
    data_ptr = (struct data_pc_st *)shm;

    pthread_t ptid[ctl_ptr->THREAD_CONS];

    for (i = 0; i < ctl_ptr->THREAD_CONS; ++i) {
    
    
        ret = pthread_create(&ptid[i], NULL, &consumer, NULL); 
        if (ret != 0) {
    
    
            perror("consumer pthread_create()");
            break;
        }
    } 
	
    for (i = 0; i < ctl_ptr->THREAD_CONS; ++i) {
    
    
        pthread_join(ptid[i], NULL);
    }

    if (shmdt(shm) == -1) {
    
    
        perror("consumer shmdt()");
        exit(EXIT_FAILURE);
    }
    
    exit(EXIT_SUCCESS);
}

int random_code(unsigned int code_len)
{
    
    
	int code_val;
	long int modulus = 1;
	
	for (int i = 0; i < code_len; i++) {
    
    
		modulus = modulus * 10;
	}
	
	srand(time(NULL));
    while (1) {
    
    
        code_val = rand() % modulus;
        if(code_val > modulus / 10 - 1) {
    
    
            break;
        }
    }
    
	return code_val;
}



コードの説明:

コードのこの部分は、THREAD_CONS コンシューマー スレッドの非同期消費プログラムに対応します. 条件がctl_ptr->cons_num < ctl_ptr->prod_num) || (ctl_ptr->END_FLAG == 0満たされた場合、つまり、消費された製品の数が生産された製品の数よりも少ない場合、または生産が終了していない場合、消費コードはキュー内のループ pop から対応する製品を転送するためにループで呼び出されます。

具体的な詳細は、コードのコメントに記載されています。

運用実績

画像-20220516211405924

実行結果からわかるように、バッファー サイズ 4、生産数 8、生産者数 2、消費者数 3 に基づいて、生産者が 8 を生産するまで生産者と消費者は非同期です。製品が消費者によって消費されると、プログラムは終了します。

おすすめ

転載: blog.csdn.net/m0_52387305/article/details/124841564