Linux中多線程使用信號量(信號燈),和sem_wait()函數使用


簡 述: 上一篇中講解了“條件變量 + 互斥量”的組合使用,演示了 “生產者-消費者”模型。本篇講解 互斥量的升級版:信號量(信號燈) 的理解和使用。互斥量與信號量的關係,可以簡單理解爲 c 和 c++ 的關係。信號量的使用的步驟,也是和前面的互斥量很像,不過這次的頭文件改爲了 #include <semaphore.h>:

  • sem_t sem; //定義變量
  • sem_wait(); //加鎖
  • …其他代碼
  • sem_post(); //解鎖
  • sem_destroy(); //銷燬

說明:

本例子是在 Linux 下面運行成功的,編譯時候,時候需要加參數 -pthread

若是想要在 Mac 運行改程序,需要改寫替換部分函數(mac 不支持其中的部分函數)


編程環境:

💻: uos20 📎 gcc/g++ 8.3 📎 gdb8.0

💻: MacOS 10.14 📎 gcc/g++ 9.2 📎 gdb8.3


信號量(信號燈):

簡單理解爲裏面具有多個互斥量的集合。是加強版的互斥鎖。

其他:

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

之間的一些解釋:

sem 變量和 函數 sem_init 中的 sem 參數是同一個;在函數裏面,第一個參數 sem 實際是和第三個參數 value 關聯的;表面上對 sem 進行修改,實際上是修改關聯的 value 的值,對其進行 ++ 或 – 操作。(嗯嗯,原理就這麼理解就行)。


使用步驟:

  1. sem_t sem;


  2. 初始化信號量

    int sem_init(sem_t *sem, int pshared, unsigned int value);
    
    • 參數:
      • pshared:
        • 0 - 線程同步
        • 1 - 進程同步
      • value:
        • 最多有幾個線程操作共享數據

  3. 加鎖:

    //調用一次相當於對 sem 做了一次 -- 操作
    int sem_wait(sem_t *sem);  //加鎖; => 如果 sem 值爲 0,線程會阻塞
    
    int sem_trywait(sem_t *sem);  //嘗試加鎖; => sem == 0,加鎖失敗,不阻塞,直接返回
    
    int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);  //限時嘗試加鎖
    

  4. 解鎖:

    int sem_post(sem_t *sem);  //相當於對於 sem 做了 ++ 操作
    

  5. 銷燬信號量

    int sem_destroy(sem_t *sem);
    

“生產者-消費者”例子:

將上一篇的例子改一改,直接使用信號量來寫這個例子“生產者-消費者”。本篇文章例子如下:


理論模型:


代碼分析:

多看一下理論模型的那個僞代碼的流程圖,多體會揣摩其中的箭頭的流向,且一開始這設置爲消費者爲阻塞,當生產者生產之後,爲其解鎖。


代碼實現:

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

sem_t semProducer;
sem_t semCustomer;

typedef struct node  //節點結構
{
    int data;
    struct node* next;
} Node;

Node* g_head =  nullptr;  //永遠指向鏈表頭部的指針

void* funProducer(void* arg);  //生產者--添加一個頭結點
void* funCustomer(void* arg);  //消費者--刪除一個頭結點

int main(int argc, char *argv[])
{
    pthread_t p1;
    pthread_t p2;

    sem_init(&semProducer, 0, 4);  //初始化生產者線程信號量, (賦予 4 個,對比下一行,讓生產者先運行)
    sem_init(&semCustomer, 0, 0);  //初始化消費者線程信號量, (賦予 0 個, 一開始就讓消費者處於阻塞狀態)

    pthread_create(&p1, nullptr, funProducer, nullptr);  //創建生產者線程
    pthread_create(&p2, nullptr, funCustomer, nullptr);  //創建消費者線程

    pthread_join(p1, nullptr);  //阻塞回收子線程
    pthread_join(p2, nullptr);

    sem_destroy(&semProducer);  //銷燬生產者信號量
    sem_destroy(&semCustomer);  //銷燬消費者信號量

    return 0;
}

void* funProducer(void* arg)
{
    while (true) {
        sem_wait(&semProducer);  //semProducer--,  == 0, 則阻塞
        Node* pNew = new Node();
        pNew->data = rand() % 1000; 
        pNew->next = g_head;
        g_head = pNew;
        printf("-----funProducer(生產者): %lu, %d\n", pthread_self(), pNew->data);
        sem_post(&semCustomer); // semCustomer++

        sleep(rand() % 3);  //隨機休息 0~2 s
    }
    
    return nullptr;
}

void* funCustomer(void* arg)
{
    while (true) {
        sem_wait(&semCustomer);  //semCustomer--,  == 0, 則阻塞
        Node* pDel = g_head;
        g_head = g_head->next;
        printf("-----funCustomer(消費者): %lu, %d\n", pthread_self(), pDel->data);
        delete pDel;
        sem_post(&semProducer); // semProducer++
    }

    return nullptr;
}

運行結果:

這個例子是在 uos20 上面運行的成功的;編譯的時候,記得加上參數 -pthread


Mac 下對 sem_init()/sem_destory() 不支持:

注意:

MacOS 不支持 sem_init()sem_destroy();這個例子若是想在 mac 下編譯通過,需要自行修改替換相關的函數。

  • sem_init(&sem, 0, 1) 改成 sem_open("sem", O_CREAT|O_EXCL, S_IRWXU, 0)
  • sem_destory(&sem) 改成 sem_unlink("sem");
  • 且支持 pthread_mutex_init(&mutex, NULL) 卻不支持 pthread_mutex_destory(&mutex)

Mac 的該文件在 /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/semaphore.h 路徑,可以查看到該頭文件的源碼,附上詳細 mac 下該庫源碼:

#ifndef _SYS_SEMAPHORE_H_
#define _SYS_SEMAPHORE_H_

typedef int sem_t;

/* this should go in limits.h> */
#define SEM_VALUE_MAX 32767
#define SEM_FAILED ((sem_t *)-1)

#include <sys/cdefs.h>

__BEGIN_DECLS
int sem_close(sem_t *);
int sem_destroy(sem_t *) __deprecated;
int sem_getvalue(sem_t * __restrict, int * __restrict) __deprecated;
int sem_init(sem_t *, int, unsigned int) __deprecated;
sem_t * sem_open(const char *, int, ...);
int sem_post(sem_t *);
int sem_trywait(sem_t *);
int sem_unlink(const char *);
int sem_wait(sem_t *) __DARWIN_ALIAS_C(sem_wait);
__END_DECLS


#endif  /* _SYS_SEMAPHORE_H_ */

下載地址:

21_semaphore

歡迎 star 和 fork 這個系列的 linux 學習,附學習由淺入深的目錄。

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