C(Unix)匿名信號量

最近編寫AWS IOT 部分代碼,發送設備消息時會引入多線程就會引入資源競爭,比如多個線程同時想發送消息進而導致資源佔用,即會導致一個線程獲得資源,另一個線程則會進入等待狀態。

而如何等待則就是一個比較細節的內容。而在維基百科上有對忙碌等待描述:進程反覆檢查一個條件是否爲真爲根本的技術。忙碌等待大部分情況很明顯是應該避免的,所以兩個線程競爭,一個線程獲取到鎖,另一個線程獲取失敗採用忙碌等待的方式去查詢狀態則會十分低效。

在同事的建議下,發現信號量就很適合這種場景下的線程同步。


1 信號量是什麼

1.1 信號量的行爲

信號量(英語:semaphore)又稱爲信號標,是一個同步對象,用於保持在0至指定最大值之間的一個計數值。當線程完成一次對該semaphore對象的等待(wait)時,該計數值減一;當線程完成一次對semaphore對象的釋放(release)時,計數值加一。當計數值爲0,則線程等待該semaphore對象不再能成功直至該semaphore對象變成signaled狀態。semaphore對象的計數值大於0,爲signaled狀態;計數值等於0,爲nonsignaled狀態。

也就是說每一次release操作 信號量就會加1 ,每次wait信號量就會-1,當wait操作後信號量等於或小於0,則會在等待函數那裏一直進行等待。而利用這種機制就可以用來進行線程間同步,比如一個線程wait 後進入等待後,另一個線程release 資源,就會喚醒前一個線程。

在Linux中信號量有兩種標準,一種是System V,另一種是Posix標準。其中System V是早期的標準;
Posix(Portable Operating System Interface )是一個由IEEE開發的一系列標準,它還是由ISO(國際標準化組織)和IEC(國際電工委員會)採納的國際標準。而System v是Unix操作系統衆多版本的一個分支。本文基於Posix標準進行設計。

1.2 信號量使用場景

semaphore對象適用於控制一個僅支持有限個用戶的共享資源,是一種不需要使用忙碌等待(busy waiting)的方法。

關於信號使用的資源開銷:
執行V操作之後若sem小於等於0,則阻塞隊列中阻塞的線程或進程個數爲|sem|+1個;而阻塞線程或進程是存放在一個阻塞鏈表中的,會保證順序依次被喚醒,這一特性就能實現大於3個數量的線程或進程實現互斥。但是由於阻塞會牽扯到睡眠,再喚醒需要線程或進程上下文切換,切換是非常耗費時間的。因此當信號量對被保護的資源佔用的時間比線程或進程切換時間長很多的時候,可以選擇應用信號量。

semaphore信號量和互斥鎖的區別:semaphore信號量會將競爭的線程掛起,保證前一個線程從阻塞態釋放後後一個線程能夠被處理。而mutex則不保證這一點(可能一個線程恰好每次輪到它執行,而對資源獨佔或者分配不均的情況)。

2 匿名信號量相關API

匿名信號主要涉及下面四個API:
sem_init
sem_wait
sem_post

int sem_init(sem_t *sem, int pshared, unsigned int value);

sem_init() 初始化一個定位在 sem 的匿名信號量
sem :指向信號量對象
pshared : 指明信號量的類型。不爲0時此信號量在進程間共享,否則只能爲當前進程的所有線程共享。
value : 指定信號量值的大小

int sem_wait(sem_t *sem);

sem_wait是一個函數,也是一個原子操作l它的作用是從信號量的值減去一個“1”,但它永遠會先等待該信號量爲一個非零值纔開始做減法。如果對一個值爲0的信號量調用sem_wait(),這個函數就會原地等待直到有其它線程增加了這個值使它不再是0爲止
sem :指向信號量對象

int sem_post(sem_t *sem);

sem_post函數的作用是給信號量的值加上一個“1”,它是一個“原子操作”---即同時對同一個信號量做加“1”操作的兩個線程是不會衝突的。
sem :指向信號量對象

int sem_destroy(sem_t *sem);

銷燬由sem指向的匿名信號量。
sem :指向信號量對象

獲取信號量值並存入 valp中

int sem_getvalue(sem_t *sem, int *valp);

3 Sample

下面是個一個多生產者 單消費者的場景。可以比較好體現信號量的使用。
效果是兩個生產產生需要發送的數據 一個產生10條,一個消費者發送這些數據。

sample 代碼創建了兩個生產者線程produce proudce2,可以理解爲實際應用在需要發送的數據產生線程。
創建了一個消費者線程consume ,可以理解爲用來發送數據的網絡線程。

這裏用到了兩個信號量,consumeSignal, produceSignal。
consumeSignal 信號量在消費者線程消費者線程調用sem_wait函數,等待生產者產生需要發送的數據。當生產者線程產生需要發送的數據後,sem_post() 喚醒消費者線程進行數據發送。
produceSignal信號量用於兩個生產這線程同步,及確保兩個生產者線程不會同時操作到共享資源。即一個生產線程產生數據到全局變量時,另個一生產者線程需要進行等待。

#include <stdio.h>
#include <semaphore.h>
#include <unistd.h>
#include <stdlib.h>
int dataBuffer[10];
int dataProduceIndex = 0;
int dataConsumeIndex = 0;
sem_t consumeSignal;
sem_t produceSignal;
void *consume(void *arg);
void *produce(void *arg);
void *produce2(void *arg);
pthread_t tidConsume;
pthread_t tidProduce1;
pthread_t tidProduce2;

int main(){
	sem_init(&consumeSignal, 0, 0); 
  sem_init(&produceSignal, 0, 1); 
  int tickClock = 0;
  pthread_create(&tidProduce1, NULL, produce, NULL);
  pthread_create(&tidProduce2, NULL, produce2, NULL);
  pthread_create(&tidConsume, NULL, consume, NULL);
  while(1){
    sleep(2);
    if(dataConsumeIndex>10){
      break;
    }
  }
	sem_destroy(&consumeSignal);
  sem_destroy(&produceSignal);

  return 0;
}
void *consume(void *arg)
{
	while(1){
    sem_wait(&consumeSignal);
    if(dataConsumeIndex > 10){
      printf("cunsume exit\n");
      pthread_exit();
    }
    printf("consume count %d send %ld by network\n", dataConsumeIndex, dataBuffer[dataConsumeIndex]);
    dataConsumeIndex++;
  }
}
void *produce(void *arg)
{
	while(1){
    sem_wait(&produceSignal);
    if(dataProduceIndex > 10){
      printf("produce exit\n");
      pthread_exit();
    }
    dataBuffer[dataProduceIndex] = random();
    printf("produce1 count %d  want send %ld \n", dataProduceIndex, dataBuffer[dataProduceIndex]);
    dataProduceIndex++;
    sem_post(&consumeSignal);
    sem_post(&produceSignal);
  }
}
void *produce2(void *arg)
{
	while(1){
    sem_wait(&produceSignal);
    if(dataProduceIndex > 10){
       printf("produce2 exit\n");
       pthread_exit();
    }
    dataBuffer[dataProduceIndex] = random();
    printf("produce2 count %d want send %ld\n", dataProduceIndex, dataBuffer[dataProduceIndex]);
    dataProduceIndex++;
    sem_post(&consumeSignal);
    sem_post(&produceSignal);
  }
}

編譯:
gcc sem.c -o test
測試 打印一下內容:

produce1 count 0  want send 1804289383
consume count 0 send 1804289383 by network
produce2 count 1 want send 846930886
consume count 1 send 846930886 by network
produce1 count 2  want send 1681692777
consume count 2 send 1681692777 by network
produce2 count 3 want send 1714636915
consume count 3 send 1714636915 by network
produce1 count 4  want send 1957747793
produce2 count 5 want send 424238335
consume count 4 send 1957747793 by network
produce1 count 6  want send 719885386
consume count 5 send 424238335 by network
produce2 count 7 want send 1649760492
consume count 6 send 719885386 by network
produce1 count 8  want send 596516649
consume count 7 send 1649760492 by network
produce2 count 9 want send 1189641421
consume count 8 send 596516649 by network
produce1 count 10  want send 1025202362
consume count 9 send 1189641421 by network
produce2 exit
consume count 10 send 1025202362 by network
cunsume exit
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章