[Linux] マルチスレッド POSIX セマフォ

1. コンセプト

セマフォ ( セマフォ ) は、
本質的に重要なリソースの数を記述するために使用されるカウンターです。

sem: 0 -> 1 -> 0クリティカル リソースが 1 つだけの場合は、sem を 1 に設定します。クリティカル リソースが使用される場合、sem は 1 から 0 に変化します。他の人は適用したいと考えていますが、保留キューに適用できません。クリティカル リソースが解放されるのを待っている場合、クリティカル リソースに再適用する前に sem は 0 から 1 に変化します。このセマフォはバイナリ セマフォと呼ばれ、ミューテックスロック
に相当します。

各スレッドは、対応するリソースにアクセスするとき、まずセマフォを申請します。申請が
成功した場合は、そのスレッドがそのリソースの使用を許可されたことを意味し
、申請が失敗した場合は、そのリソースが現在使用できないことを意味します。

2. セマフォの動作メカニズム

セマフォの仕組みは、映画を観たり、チケットを購入したりするのと同じようなリソース予約の仕組み
であり、セマフォの適用に成功すると、リソースの一部を予約したことと同じになります。

条件が満たされるかどうかを判断することで、その後の動作が決定されます。
セマフォはすでにリソースのカウンターです。セマフォ自体のアプリケーションの成功は、リソースが使用可能であることを示します。セマフォ自体のアプリケーションの失敗は、リソースが使用できないことを示します。本質は、判断を
セマフォ
のアプリケーションに変換することです

3. インターフェースを理解する

POSIX セマフォと System V セマフォは同じ機能を持ち、どちらも競合せずに共有リソースにアクセスするという目的を達成するための同期操作に使用されますが、POSIX はスレッド間の同期に使用できます。


sem_init - セマフォを初期化します

「man sem_init」と入力してください

sem: セマフォを示します。
pshared: 0 はスレッド間で共有されることを示し、0 以外はプロセス間で共有されることを示します
。 value: セマフォの初期値 (カウンタ値がどのくらいに初期化されるか)

sem_destroy - セマフォを破棄します

「man sem_destroy」と入力してください

初期化されたセマフォを破棄します

sem_wait - セマフォを申請する

「man sem_wait」と入力してください

セマフォの値を1減らすためにセマフォを申請する操作を実行します。

sem_post - セマフォの解放

man sem_postと入力してください

セマフォを解放する操作を行い、セマフォの値を1増やします。

4. リングキューに基づく生産および消費モデル

原理分析

リングキューは実際には配列を使用してシミュレートされます

配列内の余分なスペースは、完全であるかどうかを判断する問題を解決するために使用されます。


空の場合、糸と尻尾は同じ位置にあります


いっぱいの場合は、尾の次の位置が頭になります


プロデューサーはデータを末尾にプッシュします。つまり、本番
コンシューマーはデータを先頭にポップします。つまり、消費


生産者と消費者は同じリソースに関心を持っていますか?
同じではありません。生産者はリングキュー全体のスペース (店舗が商品でいっぱいかどうか) を気にし、
消費者はデータ (店舗に商品があるかどうか、商品があれば購入する) を気にします。


頭と尾が同じ領域にアクセスするのはいつですか?
時計のように0時から12時までの目盛りがついた大きなテーブルがあり、それぞれの目盛りに皿が置かれています。AとBの2人が同時に部屋に入ってきてテーブルを見ます。Aはその皿にリンゴを置き、Bはそのリンゴを後ろに持ちます。AとBは同意します:BはAを超えてはならず、皿にはリンゴは1個だけ置くことが
でき
ます

A と B が開始するとき、テーブルにリンゴがないとき、またはテーブルがリンゴでいっぱいのとき、彼らは同じプレートを訪問します。つまり、リング キューが空であるか、リング キューがいっぱいで、同じエリアを訪問し
ます

キューが空で同じ場所を指している場合、競合関係があり、
プロデューサーを最初に実行します (プロデューサーがデータを生成した後にのみ、コンシューマーはデータを取得できます)。

キューがいっぱいの場合は、同じ場所を指し、競合関係があり、
コンシューマを最初に実行します(プロデューサは、コンシューマがデータを取得した後にのみ生成できます)。


プロデューサはスペースを気にしており、スペース自体もリソースであるため、プロデューサに対してセマフォ sem_room を定義する必要があり、その初期値は N
P(sem_room) です —— スペース セマフォを申請します

プロデューサのプロダクション データが現在の空間にある場合、対応するデータは +1 になるため、コンシューマはデータ
V (sem_data) - データ セマフォの値 +1を取得できます。

消費者はデータを重視しており、セマフォは sem_data であり、その初期値は 0
P(sem_data) です —— データ セマフォを申請します

コンシューマはデータを奪い、現在のスペースはアイドル状態であるため、プロデューサはデータ
V(sem_room) - スペース セマフォの値 +1を置くことができます。

コード

コード分​​析

まずringqueue.hppにringqueueクラスを作成します。


main 関数で new を使用して rq キューを作成します。
プロデューサーとコンシューマーが同じリソースを確実に認識できるようにするために、両方のコールバック関数のパラメーター引数は rq です。


productorRoutine のコールバック関数は、キュー rq のプッシュを使用してデータをキューに挿入します。つまりproduction


リングキュークラス

リングキュークラス内

上記の原理を説明すると、コンシューマのみがデータ セマフォを気にし、プロデューサのみがスペース セマフォを気にします。

構造

リングキューのリングサイズと_cap(容量)をNに初期化します。

0はスレッド間での共有を意味し、データセマフォは0に初期化、スペースセマフォはリングキュー全体の容量に初期化されます
(両者の初期化値については原理編に詳しい説明があります)

破壊する

セマフォは構築時に初期化されるため、セマフォを破棄する必要があります

プッシュ - 生産

生産前に生産を実行する前に条件が満たされていることを確認する必要があるため、P 操作が必要です - セマフォの申請

セマフォを使用する場合、セマフォはカウンタであるため判断する必要はなく、要はリソースの準備状況をクリティカルエリア内からクリティカルエリア外へ転送することであり、それ自体がクリティカルリソースの数を記述するため、クリティカルエリアに入った後にクリティカルリソースが条件を満たしているかどうかを判断する必要がない。


生産者と消費者は同じ場所を訪れることもありますが、高い確率で異なる場所を訪れることもあるため
生産者と消費者は自分の場所を示すための独自の添え字を持つ必要があります。


P 操作を継続的に実行してスペースにデータを挿入します。
スペースがない場合、コンシューマーは消費 (V 操作) してデータを取り出す必要があります。

末尾が追加のスペース位置に到達すると、実際には再び先頭に戻ることと同じになるため、
%= を使用してリング キューをシミュレートします。


関数 P および V を使用して sem_wait および sem_post をカプセル化します
。再度使用する場合は、PV を呼び出すだけで実現できます。

ここに画像の説明を挿入

ポップ - 消費

P 操作を継続的に実行してスペースからデータを削除し、スペースがアイドル状態になったら、
プロデューサーはデータを生成 (V 操作) してスペースに配置する必要があります。

コード

リングキュー.hpp


#include<iostream>
#include<vector>
#include<semaphore.h>//信号量头文件
static const int N=5;//设置环形队列的大小

template<class T>
class ringqueue
{
    
    
private:
  void P(sem_t &s)
  {
    
    
     sem_wait(&s); 
  }
  void V(sem_t&s)
  {
    
    
     sem_post(&s);
  }
public:
    ringqueue(int num=N)
    : _ring(num),_cap(num)
    {
    
    
        //信号量初始化
        sem_init(&_data_sem,0,0);
        sem_init(&_space_sem,0,num);
        _c_step=_p_step=0;//生产和消费下标都为0
    }
    ~ringqueue()
    {
    
    
        //销毁信号量
     sem_destroy(&_data_sem);
     sem_destroy(&_space_sem);
    }

    void push(const T&in)//生产
    {
    
    
      P(_space_sem);//P操作 申请信号量
      _ring[_p_step++]=in;//将数据放入生产位置
      _p_step%=_cap;
      V(_data_sem);//V操作 释放信号量
    }
    void pop(T*out)//消费 
    {
    
    
       P(_data_sem);//P操作
       *out=_ring[_c_step++];//将该位置的数据给与out
       _c_step%=_cap;
       V(_space_sem);//V操作
    }
private:
   int _c_step;//消费者位置下标
   int _p_step;//生产者位置下标
   std::vector<int> _ring;//充当环形队列
   int  _cap;//环形队列的容器大小
   sem_t _data_sem;//数据信号量
   sem_t _space_sem;//空间信号量 
};

メイクファイル

ringqueue:main.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f ringqueue 

main.cc


#include"Ringqueue.hpp"
#include<pthread.h>
#include<unistd.h>
using namespace std;
void*consumerRoutine(void*args)
{
    
    
  ringqueue<int>*rq=(ringqueue<int>*)args;
  while(true)
  {
    
    
  int data=0;
  rq->pop(&data);//从队列取出数据 消费
  cout<<"consumer done:"<<data<<endl;
  sleep(1);
  }
}
void*productorRoutine(void*args) 
{
    
    
 ringqueue<int>*rq=(ringqueue<int>*)args;
 while(true)
 {
    
    
   int data=1;
   rq->push(data);//将数据插入队列中 生产
   cout<<"productor done:"<<data<<endl;
 }
}

int main()
{
    
    
   ringqueue<int>*rq=new ringqueue<int>();
   pthread_t c;//消费者
   pthread_t p;//生产者
   //创建线程
   pthread_create(&c,nullptr,consumerRoutine,rq);
     pthread_create(&p,nullptr,productorRoutine,rq);

     pthread_join(c,nullptr);
     pthread_join(p,nullptr);
    return 0;
}

おすすめ

転載: blog.csdn.net/qq_62939852/article/details/131754311