簡 述: 上一篇中講解了“條件變量 + 互斥量”的組合使用,演示了 “生產者-消費者”模型。本篇講解 互斥量的升級版:信號量(信號燈) 的理解和使用。互斥量與信號量的關係,可以簡單理解爲 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 的值,對其進行 ++ 或 – 操作。(嗯嗯,原理就這麼理解就行)。
使用步驟:
-
sem_t sem;
-
初始化信號量
int sem_init(sem_t *sem, int pshared, unsigned int value);
- 參數:
- pshared:
- 0 - 線程同步
- 1 - 進程同步
- value:
- 最多有幾個線程操作共享數據
- pshared:
- 參數:
-
加鎖:
//調用一次相當於對 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); //限時嘗試加鎖
-
解鎖:
int sem_post(sem_t *sem); //相當於對於 sem 做了 ++ 操作
-
銷燬信號量
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_ */
下載地址:
歡迎 star 和 fork 這個系列的 linux 學習,附學習由淺入深的目錄。