1. 信號量簡介
信號量用於進程或線程間同步,Posix信號量是一個非負整型,只有兩種操作,加一(sem_post)和減一(sem_wait),如果信號量值爲0,sem_wait默認阻塞。
Posix信號量有兩種,有名信號量和無名信號量,顧名思義,就是是否有名字。有名信號量有一個名字,長度不超過NAME_MAX-4(i.e. 251),因爲內核會默認加上'sem.',所以這裏要減4,名字以斜槓開頭'/',後面跟上一個或多個非斜槓字符。不同進程可以通過同一個名字來操作有名信號量,sem_open用於創建或者獲取已存在的信號量,創建好之後就可以使用sem_post或者sem_wait來操作。使用完之後可以使用sem_close來關閉信號量,sem_unlink用來刪除信號量,刪除並不立即銷燬,只有當所有進程都sem_close纔開始銷燬。
無名信號量沒有名字,基於內存,通常用在同一個進程線程之間或者不同進程的共享內存裏,因爲同一個進程的不同線程共享進程地址空間,所以可以訪問到,放到共享內存裏也可以被不同進程訪問到。使用前必須使用sem_init進行初始化,初始化之後就可以使用sem_wait和sem_post操作,使用完成後sem_destroy接口進行銷燬,下一節介紹接口的使用
2. 信號量API接口
2.1 有名信號量創建
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
/**
* @brief 創建或獲取有名信號量
*
* @params name 有名信號量關聯的名字,斜槓開頭,長度不超過NAME_MAX-4(e.g. 251)
* @params oflag 標誌位,可選值包括O_CREAT| O_EXCL
* 這裏如果指定了O_CREAT標誌位,還要填寫額外兩個參數,mode和value
*
* @params mode,參考open函數,通常填0即可
* @params value 信號量的初始值
* @returns 成功返回描述符,失敗返回-1
**/
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,
mode_t mode, unsigned int value);
# 編譯加上 -pthread選項
Link with -pthread.
2.2 信號量減一操作
#include <semaphore.h>
/**
* @brief lock信號量並減1,當信號量大於0,操作可以執行,否則則阻塞。如果設置了NONBLOCK標誌位,則報錯返回
*
* @params sem 信號量描述符
* @returns 成功返回0,失敗返回-1
**/
int sem_wait(sem_t *sem);
/**
* @brief 同sem_wait,只不過如果無法減1則立即報錯返回
*
**/
int sem_trywait(sem_t *sem);
/**
* @brief 同sem_wait,只不過如果無法減1則會等待一段時間,注意這裏時間參數要設置正確
*
**/
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
# 編譯鏈接選項 -pthread
Link with -pthread.
2.3 信號量加1操作
#include <semaphore.h>
/**
* @brief 信號量的值加1
*
* @params sem 信號量文件描述符
* @returns 成功返回0,失敗返回-1
**/
int sem_post(sem_t *sem);
# 編譯鏈接選項 -pthread
Link with -pthread.
2.4 關閉有名信號量
#include <semaphore.h>
/**
* @brief 關閉信號量,系統爲當前進程分配的信號量資源會被釋放。
*
* @params sem 信號量文件描述符
* @returns 成功返回0,失敗返回-1
**/
int sem_close(sem_t *sem);
# 編譯鏈接選項 -pthread
Link with -pthread.
2.5 銷燬有名信號量
#include <semaphore.h>
/**
* @brief 銷燬信號量,只有當所有進程都關閉信號量之後纔開始銷燬工作
*
* @params name 信號量名字
* @returns 成功返回0,失敗返回-1
**/
int sem_unlink(const char *name);
# 編譯鏈接選項 -pthread
Link with -pthread.
2.6 初始化無名信號量
#include <semaphore.h>
/**
* @brief 初始化無名信號量
*
* @params sem 待初始化的信號量地址
* @params pshared 爲0表示線程間共享,非0表示進程間共享
* @params value 信號量初始值,不超過SEM_VALUE_MAX
* @returns 成功返回0,失敗返回-1
**/
int sem_init(sem_t *sem, int pshared, unsigned int value);
# 編譯鏈接選項 -pthread
Link with -pthread.
2.7 銷燬無名信號量
#include <semaphore.h>
/**
* @brief 銷燬無名信號量
*
* @params sem 要銷燬的信號量
* 如果有其它線程或進程阻塞在sem上,此時銷燬會產生未定義的行爲
*
* @returns 成功返回0,失敗返回-1
**/
int sem_destroy(sem_t *sem);
# 編譯鏈接選項 -pthread
Link with -pthread.
3 有名信號量例子
3.1 信號量生產者
// 生產者
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#define SEM_NAME "/sem0"
int main(int argc, char** argv)
{
if (argc < 3)
{
printf("Usage: ./sem_post timeval nums\n");
return -1;
}
int ts = atoi(argv[1]);
int total = atoi(argv[2]);
if (total < 1 || ts < 1)
{
printf("invalid param\n");
return -1;
}
sem_t* sem_id;
// 創建信號量並初始化值爲0
sem_id = sem_open(SEM_NAME, O_CREAT, O_RDWR, 0);
if (sem_id == SEM_FAILED)
{
perror("sem_open error");
return -1;
}
int curr = 0;
while (curr < total)
{
// 生成信號量,即加1
while (sem_post(sem_id))
{
perror("sem_post error, try later");
sleep(1);
}
printf("producing succ\n");
sleep(ts);
++curr;
}
printf("work done\n");
// 關閉信號量
sem_close(sem_id);
return 0;
}
3.2 信號量消費者
// 消費者
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#define SEM_NAME "/sem0"
int main()
{
sem_t* sem_id;
// 創建信號量並初始化值爲0
sem_id = sem_open(SEM_NAME, O_CREAT, O_RDWR, 0);
if (sem_id == SEM_FAILED)
{
perror("sem_open error");
return -1;
}
while (1)
{
// 消費信號量
if (sem_wait(sem_id))
{
perror("sem_wait fail, try later\n");
sleep(1);
continue;
}
printf("consuming succ\n");
}
// 關閉信號量
sem_close(sem_id);
return 0;
}
3.3 編譯&運行
default:
gcc -o sem_post sem_post.c -pthread
gcc -o sem_wait sem_wait.c -pthread
clean:
rm -rf sem_wait sem_post
4. 無名信號量例子
創建兩個線程,分別用於生產和消費
#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
// 消費者
void* consumer_worker(void *arg)
{
sem_t *sem = arg;
while (1)
{
// 消費信號量
if (sem_wait(sem))
{
perror("[consumer] sem_wait error, try later\n");
sleep(1);
continue;
}
printf("[consumer] consume succ\n");
}
return 0;
}
// 生產者
void* producer_worker(void *arg)
{
sem_t *sem = arg;
while (1)
{
// 生成信號量
if (sem_post(sem))
{
perror("[producer] sem_post error, try later\n");
sleep(1);
continue;
}
printf("[producer] produce succ\n");
sleep(1);
}
return 0;
}
int main(int argc, char** argv)
{
pthread_t consumer, producer;
if (argc < 2)
{
printf("Usage: ./unnamed_sem time\n");
return -1;
}
int tm = atoi(argv[1]);
if (tm < 1)
{
printf("invalid param\n");
return -1;
}
sem_t sem;
// 無名信號量初始化
if (sem_init(&sem, 0, 0))
{
perror("sem_init error");
return -1;
}
// 創建生產者線程
if (pthread_create(&producer, NULL, &producer_worker, (void *)&sem))
{
perror("create producer_worker error");
sem_destroy(&sem);
return -1;
}
// 創建消費者線程
if (pthread_create(&consumer, NULL, &consumer_worker, (void *)&sem))
{
perror("create consumer_worker error");
sem_destroy(&sem);
return -1;
}
printf("main thread sleep:%d\n", tm);
sleep(tm);
// 無名信號量銷燬
sem_destroy(&sem);
return 0;
}
5. 參考資料
1. https://www.man7.org/linux/man-pages/man7/sem_overview.7.html
2. 《Linux環境編程 從應用到內核》
================================================================================================
Linux應用程序、內核、驅動、後臺開發交流討論羣(745510310),感興趣的同學可以加羣討論、交流、資料查找等,前進的道路上,你不是一個人奧^_^。