【Linux】posix信號量,線程的二義性一次解決!!

1.什麼是posix信號量?

  1. posix信號量可以完成進程間和線程間的同步與互斥
  2. 本質:信號量 = 資源計數器 (判斷資源是否可用) + PCB等待隊列 (生產者、消費者和lock) + 等待和喚醒接口
  3. 對比條件變量cond,多一個資源計數器,這個資源計數器用來對臨界資源進行計數。信號量通過判斷自身的資源計數器,來進行條件判斷,判斷當前資源是否可用。因此也就省去了while()這樣循環判定資源的情況。
    計數器大於零:則進行資源訪問
    計數器小於零:則進行阻塞等待,直到被計數器++,大於零

2.posix信號量操作接口

前提: 在bash中輸入ipcs -s 看到的就是信號量
進程間本質: 在共享內存中創建的一塊,因此可以實現進程間同步和互斥
線程間本質: 定義成全局變量或者類的成員變量,因爲各個線程同一塊虛擬地址空間

  1. 定義:
    sem_t sem;定義了一個posix信號量
  2. 初始化:
    sem_init(sem_t* sem,int pshared,int value)
    用例:sem_init(con_,0,0);
    sem:傳入信號量地址
    pshared:表示當前信號量是用在進程間還是線程間
    0:線程間使用
    !0:進程間使用
    value:用於初始化信號量當中資源計數器,表示實際的資源的數量






  3. 等待: wait接口用於請求“加鎖”,加鎖則計數器 - -
    sem_wait(sem_t* sem)–>阻塞等待
    sem_trywait(sem_t* sem)–>非阻塞等待
    sem_timedwait(sem_t* sem,const struct timespec* timeval)–>帶有超時時間的等待


  4. 喚醒: post接口用於“解鎖”,並喚醒隊列,解鎖則計數器++
    int sem_post(sem_t* sem);發佈信號量,表示資源使用完成了,即:歸還資源或者生產者重新生產了一個資源,對信號量中的資源計數器+1,並且喚醒PCB等待隊列當中的PCB。
    簡而言之:歸還或者生產了一個,每發佈一個信號:資源計數器進行+1,wait接口裏面-1

  5. 銷燬:
    sem_destroy(sem_t* sem);釋放信號量開闢的內存

值得注意的是:
1.進程間: 當使用sem_init初始化信號量爲進程間時,會在內核中創建一塊共享內存,來保存信號量的數據結構。其中資源計數器,PCB等待隊列都是在共享內存當中維護的。所以我們調用喚醒或者等待接口的時候,就通過操作共享內存實現了不同進程之間的通信,進而實現不同進程之間的同步與互斥。
2.線程間: 定義一個全局變量或者類的成員變量則都能訪問到,因爲各個線程都同一塊虛擬地址空間
3.調用 wait 接口之後,如果資源計數器的值 > 0 則成功獲取信號量,如果資源計數器 < = 0 則阻塞該線程,進入PCB等待隊列,直到被喚醒
4.調用 wait 接口進行獲取信號量,不論是否獲取成功,都會對資源計數器-1



3.posix是如何保證同步和互斥的?

3.1 互斥屬性

在這裏插入圖片描述
互斥:lock_
初始化時,必須初始化信號量中資源計數器的值爲1,表示同一時刻只能有一個訪問lock信號量
sem_init(&lock_,0,1);


3.2 同步屬性

在這裏插入圖片描述
同步:pro_,con_
初始化的時候,根據資源的容量來進行初始化posix信號量當中的資源計數器
例如:資源容量爲5,則生產者posix信號量資源計數器初始化爲5。消費者posix信號量資源計數器初始化爲0,表示當前無資源可用,等生產者訪問完臨界資源之後,會通知消費者sem,計數器進行++
sem_init(&pro_,0,capacity_);
sem_init(&con_,0,0);




4.實現生產消費模型

4.1 前提:使用POSIX信號量

  1. POSIX信號量初始化階段,會初始化一個資源的數量,意味着給資源計數器附一個初始值
  2. 每次獲取臨界資源之前,都需要先去獲取信號量,獲取信號量時,需要調用等待接口
    如果等待接口沒有返回:阻塞,則表示不能獲取信號量(資源計數器的值<=0)
    如果等待接口返回:則表示已經獲取信號量,可以正常訪問臨界資源

  3. 在訪問完成臨界資源之後,調用喚醒接口,會對信號量當中的計數器進行+1操作
  4. 釋放信號量的內存

4.2 使用信號量實現生產者與消費者模型

  1. 線程安全的隊列
    數組+先進先出的特性
    posWrite、posRead
    用PosWrite=(POSWrite+1)%capacity 計算位置,循環讀寫


  2. 互斥
    sem_t lock;互斥–>初始化後資源數爲1

  3. 同步,消費者和生產者
    sem_t Consume;消費者PCB等待隊列
    讀:sem_init(&Consume,0,0);最後一個參數要初始化爲0,表示當前沒有資源可用
    sem_post(&Product):生產者的計數器+1,通知生產者PCB等待隊列


    sem_t Product;生產者PCB等待隊列
    寫:sem_init(&Product,0,capacity);
    sem_post(&Consume):可讀的空間+1,通知消費者PCB等待隊列

代碼實現:

#include<stdio.h>
#include<iostream>
#include<vector>
#include<unistd.h>
#include<semaphore.h>
#include<pthread.h>

#define THREADCOUNT 4

class RingQueue
{
   
   
private:
  std::vector<int> vec_;
  size_t Capacity_;
  sem_t lock_;
  sem_t pro_;
  sem_t con_;
  int WritePos;
  int ReadPos;
public:
  RingQueue(size_t SIZE)
    :vec_(SIZE)
    ,Capacity_(SIZE)
  {
   
   
    sem_init(&lock_,0,1);  //第二個參數屬性一般爲0,第三個參數初始化數量
    sem_init(&pro_,0,SIZE);
    sem_init(&con_,0,0);
    WritePos=0;
    WritePos=0;
  }
  ~RingQueue()
  {
   
   
    sem_destroy(&lock_);
    sem_destroy(&pro_);
    sem_destroy(&con_);
  }
  void Push(int& data)
  {
   
   
    sem_wait(&pro_);
    sem_wait(&lock_);
    vec_[WritePos]=data;
    WritePos=(WritePos+1)%Capacity_;
    sem_post(&lock_);
    sem_post(&con_);
  }
  void Pop(int* data)
  {
   
   
    sem_wait(&con_);
    sem_wait(&lock_);
    *data=vec_[ReadPos];
    ReadPos=(ReadPos+1)%Capacity_;
    sem_post(&lock_);
    sem_post(&pro_);
  }
};
void* ProStart(void*arg)
{
   
   
  RingQueue* rq=(RingQueue*)arg;
  int data=1;
  while(1)
  {
   
   
    rq->Push(data);
    printf("pro:[%d]~~~\n",data);
    data++;
    sleep(1);
  }
  return NULL;
}
void* ConStart(void*arg)
{
   
   
  RingQueue* rq=(RingQueue*)arg;
  int data;
  while(1)
  {
   
   
    rq->Pop(&data);
    printf("---con:[%d]---\n",data);
    sleep(1);
  }
  return NULL;
}
int main()
{
   
   
  RingQueue* rq=new RingQueue(4);
  pthread_t pro_tid[THREADCOUNT];
  pthread_t con_tid[THREADCOUNT];
  for(int i=0;i<THREADCOUNT;i++)
  {
   
   
    pthread_create(&pro_tid[i],NULL,ProStart,(void*)rq);
    pthread_create(&con_tid[i],NULL,ConStart,(void*)rq);
  }
  for(int i=0;i<THREADCOUNT;i++)
  {
   
   
    pthread_join(pro_tid[i],NULL);
    pthread_join(con_tid[i],NULL);
  }
  delete rq;
  rq=NULL;
  return 0;
}

5.實現互斥和同步的模板寫法

  1. Push:往隊列中存儲數據
    前提: sem_init(&ProSem_,0, 數組元素個數);------初始化資源計數器和數組容量相同,代表可以多次生產
    1) sem_wait(&ProSem_);------生產者計數器–,如果計數器>0,則繼續往下,否則入隊等待
    2)sem_wait(&lock_); ------lock計數器–,鎖一般只能有一個訪問,若<0則入隊等待
    3)arr[PosWrite] = 10;------訪問臨界資源
    4)sem_post(&lock_); ------“解鎖”,計數器++,通知lock等待隊列
    5)sem_post(&ConSem_);------消費者計數器++,通知消費者隊列





  2. Pop:從隊列中獲取數據
    前提: sem_init(&ConSem_,0,0);------初始化消費者計數器爲0,代表當前沒有資源可以使用。等生產者調用post會使這裏的計數器++。
    1)sem_wait(&ConSem_); ------.消費者計數器–,如果計數器>0,則繼續往下,否則入隊等待
    2)sem_wait(&lock_); ------lock
    3)arr[PosRead_]; ------訪問
    4)sem_post(&lock_); ------“解鎖”,計數器++,通知lock等待隊列
    5)sem_post(&ProSem_); ------生產者計數器++,通知消費者隊列





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章