??博客主頁(yè)??:??https://blog.csdn.net/wkd_007??
??博客內(nèi)容??:??嵌入式開發(fā)、Linux、C語(yǔ)言、C++、數(shù)據(jù)結(jié)構(gòu)、音視頻??
??本文內(nèi)容??:??介紹 ??
??金句分享??:??你不能選擇最好的,但最好的會(huì)來(lái)選擇你——泰戈?duì)??
?發(fā)布時(shí)間?:
本文未經(jīng)允許,不得轉(zhuǎn)發(fā)!??!
??一、概述
信號(hào)量是由E.W.Dijkstra
為互斥和同步的高級(jí)管理提出的概念。它支持兩種原子操作,一個(gè)是wait操作(減少信號(hào)量的值),另一個(gè)是post操作(增加信號(hào)量的值)。
一般來(lái)說(shuō), 信號(hào)量是和某種預(yù)先定義的資源相關(guān)聯(lián)的。信號(hào)量元素的值,表示與之關(guān)聯(lián)的資源的個(gè)數(shù)。內(nèi)核會(huì)負(fù)責(zé)維護(hù)信號(hào)量的值,并確保其值不小于0。
?1.1 二值信號(hào)量、計(jì)數(shù)信號(hào)量
信號(hào)量按照初始化的信號(hào)量值,可以分為使用二值信號(hào)量
(binary semaphore)和計(jì)數(shù)信號(hào)量
(counting semaphore)
- 二值信號(hào)量:是使用最廣泛的信號(hào)量。 對(duì)于這種信號(hào)量而言,它只有兩種合法值:0和1,對(duì)應(yīng)一個(gè)可用的資源。若當(dāng)前有資源可用,則與之對(duì)應(yīng)的二值信號(hào)量的值為1;若資源已被占用,則與之對(duì)應(yīng)的二值信號(hào)量的值為0。
- 計(jì)數(shù)信號(hào)量:資源個(gè)數(shù)超過(guò)1個(gè)的信號(hào)量。假設(shè)計(jì)數(shù)信號(hào)量初始化的信號(hào)量值為5,表示該信號(hào)量有6中合法值:0、1、2、3、4、5。當(dāng)取值為0時(shí),表示沒(méi)有資源可用了;其他合法值則表示資源的剩余數(shù)量。
?1.2 System V信號(hào)量、POSIX信號(hào)量
Linux系統(tǒng)中提供了兩個(gè)信號(hào)量實(shí)現(xiàn),一種是System V信號(hào)量
,另一種是POSIX信號(hào)量
,它們的作用是相同的,都是用于同步進(jìn)程之間及線程之間的操作,以達(dá)到無(wú)沖突地訪問(wèn)共享資源的目的。
下面是System V信號(hào)量
的相關(guān)接口函數(shù):
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
int semctl(int semid, int semnum, int cmd,/* union semun arg*/);
int semop(int semid, struct sembuf *sops, unsigned nsops);
POSIX信號(hào)量
提供了兩類: 有名信號(hào)量和無(wú)名信號(hào)量。
有名信號(hào)量由于其有名字, 多個(gè)不相干的進(jìn)程可以通過(guò)名字來(lái)打開同一個(gè)信號(hào)量, 從而完成同步操作, 所以有名信號(hào)量的操作要方便一些, 適用范圍也比無(wú)名信號(hào)量更廣。
有名信號(hào)量的函數(shù)接口與無(wú)名信號(hào)量基本相同,就是初始化和銷毀有區(qū)別,下面是有名信號(hào)量的初始化、銷毀接口:
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
sem_close(sem_p);
sem_unlink(sem_p);
而無(wú)名信號(hào)量,由于沒(méi)有名字多用于線程之間,也是本文重點(diǎn)節(jié)點(diǎn)的信號(hào)量,下文都是所說(shuō)的信號(hào)量,都特指這種用于多線程同步的無(wú)名信號(hào)量
。
??二、無(wú)名信號(hào)量
無(wú)名信號(hào)量, 又稱為基于內(nèi)存的信號(hào)量
,由于其沒(méi)有名字,沒(méi)法通過(guò)open操作直接找到對(duì)應(yīng)的信號(hào)量,所以很難直接用于沒(méi)有關(guān)聯(lián)的兩個(gè)進(jìn)程之間。無(wú)名信號(hào)量多用于線程之間的同步。因?yàn)榫€程會(huì)共享地址空間, 所以訪問(wèn)共同的無(wú)名信號(hào)量是很容易辦到的事情。
?2.1 初始化無(wú)名信號(hào)量 | sem_init
無(wú)名信號(hào)量的初始化是通過(guò)sem_init
函數(shù)來(lái)完成的。
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
Link with -pthread.
- 函數(shù)描述:初始化
sem
指針指向的無(wú)名信號(hào)量。 - 函數(shù)參數(shù):
- sem:要初始化的無(wú)名信號(hào)量地址;
- pshared:用于聲明信號(hào)量是在線程間共享還是在進(jìn)程間共享。0表示在線程間共享,非零值則表示信號(hào)量將在進(jìn)程間共享。 要想在進(jìn)程間共享,信號(hào)量必須位于共享內(nèi)存區(qū)域內(nèi)。
- value:指定的信號(hào)量初始值。
- 返回值:成功返回 0, 失敗返回 -1 并設(shè)置errno。
?2.2 銷毀無(wú)名信號(hào)量 | sem_destroy
銷毀無(wú)名信號(hào)量的接口定義如下:
#include <semaphore.h>
int sem_destroy(sem_t *sem);
- 函數(shù)描述:銷毀
sem
指針指向的無(wú)名信號(hào)量。必須是sem_init函數(shù)初始化過(guò)的。 - 函數(shù)參數(shù):
- sem:要銷毀的無(wú)名信號(hào)量地址;
- 返回值:成功返回 0, 失敗返回 -1 并設(shè)置errno。
?2.3 等待信號(hào)量 | sem_wait
信號(hào)量總是和某種資源關(guān)聯(lián)在一起,申請(qǐng)資源時(shí),需要先調(diào)用sem_wait
函數(shù)。函數(shù)原型如下:
#include <semaphore.h>
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
- 函數(shù)描述:這三個(gè)函數(shù)都是用于等待信號(hào)量, 它會(huì)將信號(hào)量的值減1。如果函數(shù)正處于阻塞,被信號(hào)中斷,則返回-1,并且置errno為EINTR。
- sem_wait:若信號(hào)量值大于0, 那么
sem_wait
函數(shù)將信號(hào)量的值減1之后會(huì)立刻返回。否則sem_wait
函數(shù)陷入阻塞,待信號(hào)量的值大于0之后,再執(zhí)行減1操作,然后成功返回。 - sem_trywait:若信號(hào)量值大于0,那么
sem_trywait
函數(shù)將信號(hào)量的值減1之后會(huì)立刻返回。否則sem_trywait
立刻返回失敗, 并置errno
為EAGAIN
。 - sem_timedwait:若信號(hào)量值大于0,那么
sem_timedwait
函數(shù)將信號(hào)量的值減1之后會(huì)立刻返回。否則sem_timedwait
會(huì)等待一段時(shí)間,如果超過(guò)了等待時(shí)間,信號(hào)量的值仍為0,那么返回 -1,并置errno
為ETIMEOUT
。
- sem_wait:若信號(hào)量值大于0, 那么
- 函數(shù)參數(shù):
- sem:要等待的無(wú)名信號(hào)量地址;
- abs_timeout:是一個(gè)絕對(duì)時(shí)間,可以使用gettimeofday函數(shù)或clock_gettime函數(shù)獲取當(dāng)前時(shí)間,再加上想等待的時(shí)間,最后將相加的值轉(zhuǎn)換成struct timespec類型傳給
sem_timedwait
。
- 返回值:成功返回 0, 失敗返回 -1 并設(shè)置errno。
?2.4 發(fā)布信號(hào)量 | sem_post
前面介紹了信號(hào)量申請(qǐng)資源時(shí)要調(diào)用的函數(shù),這小節(jié)介紹歸還資源時(shí)信號(hào)量調(diào)用的函數(shù) sem_post ,函數(shù)原型如下:
#include <semaphore.h>
int sem_post(sem_t *sem);
- 函數(shù)描述:用于發(fā)布信號(hào)量,表示已經(jīng)完成了對(duì)資源使用,可以歸還資源了。
如果發(fā)布信號(hào)量之前, 信號(hào)量的值是0,并且已經(jīng)有線程正等待在該信號(hào)量上,調(diào)用sem_post
之后,會(huì)有一個(gè)線程被喚醒,被喚醒的線程會(huì)繼續(xù)sem_wait
函數(shù)的減1操作。 如果有多個(gè)線程正等待在信號(hào)量上,那么將無(wú)法確認(rèn)哪個(gè)線程會(huì)被喚醒。 - 函數(shù)參數(shù):
- sem:要發(fā)布的無(wú)名信號(hào)量地址;
- 返回值:成功返回 0, 失敗返回 -1 并設(shè)置errno。
參數(shù)指向非法的信號(hào)量地址時(shí),會(huì)置errno為EINVAL。
當(dāng)信號(hào)量的值超過(guò)上限(即超過(guò)INT_MAX)時(shí),置errno
為EOVERFLOW
。
?2.5 獲取信號(hào)量的值 | sem_getvalue
信號(hào)量的值可以通過(guò) sem_getvalue 獲取,函數(shù)原型如下:
#include <semaphore.h>
int sem_getvalue(sem_t *sem, int *sval);
- 函數(shù)描述:
sem_getvalue
函數(shù)會(huì)返回當(dāng)前信號(hào)量的值, 并將值寫入sval指向的變量.
如果值大于0,表示不需要等待;如果值為0,表示再申請(qǐng)資源時(shí)需要等待。這個(gè)值不會(huì)為負(fù)數(shù),并且其返回的值可能已經(jīng)過(guò)時(shí)了。 - 函數(shù)參數(shù):
- sem:要獲取值的無(wú)名信號(hào)量地址;
- sval:傳出參數(shù),用于存放信號(hào)量值的int型地址。
- 返回值:成功返回 0, 失敗返回 -1 并設(shè)置errno。
??三、二值信號(hào)量的使用例子
首先了解一下什么是臨界區(qū),所謂臨界區(qū), 是指同一時(shí)間只能容許一個(gè)線程進(jìn)入的一系列操作。
二值信號(hào)量是最常用的信號(hào)量,在Linux多線程編程中,二值信號(hào)量主要有兩種用法:一是可以像互斥量一樣,對(duì)臨界區(qū)加鎖,防止多個(gè)線程并發(fā)進(jìn)入臨界區(qū)。二是可以像條件變量一樣,在“生產(chǎn)者-消費(fèi)者”模式的多個(gè)線程進(jìn)行同步地訪問(wèn)共享資源。
?3.1 信號(hào)量在臨界區(qū)的使用
下面代碼是使用信號(hào)量來(lái)加鎖臨界區(qū),使多個(gè)線程不會(huì)并發(fā)地進(jìn)入臨界區(qū)操作。這個(gè)用法看起來(lái)很像互斥量。
// 10_sem_mutex.c
// gcc 10_sem_mutex.c -l pthread
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
int g_Count = 0;
sem_t g_sem;
void *func(void *arg)
{
int i=0;
for(i=0; i<10000000; i++)
{
sem_wait(&g_sem);
g_Count++;
sem_post(&g_sem);
}
return NULL;
}
int main()
{
sem_init(&g_sem, 0, 1);
// 創(chuàng)建4個(gè)線程
pthread_t threadId[4];
int i=0;
for(i=0; i<4; i++)
{
pthread_create(&threadId[i], NULL, func, NULL);
}
for(i=0; i<4; i++)
{
pthread_join(threadId[i],NULL);
printf("join threadId=%lx\n",threadId[i]);
}
printf("g_Count=%d\n",g_Count);
sem_destroy(&g_sem);
return 0;
}
運(yùn)行結(jié)果如下,從結(jié)果看,也是發(fā)揮了鎖住臨界區(qū)的作用:
?3.2 信號(hào)量在“生產(chǎn)者-消費(fèi)者”模式的使用
下面代碼是信號(hào)量在“生產(chǎn)者-消費(fèi)者”模式的使用,一些線程等待信號(hào)量,在另一些線程發(fā)布信號(hào)量。代碼是參考上篇文章介紹條件變量的示例代碼修改的,感興趣的去可以看看。
代碼里也有使用到互斥量,因?yàn)榇嬖诙鄠€(gè)線程訪問(wèn)共享資源的情況,雖然也可以使用另一個(gè)信號(hào)量來(lái)做互斥,但那樣的代碼看起來(lái)就很困難。
// 10_producer_consumer_sem.c
// gcc 10_producer_consumer_sem.c -lpthread
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <errno.h>
#include "linux_list.h"
#define COMSUMER_NUM 2
typedef struct _product
{
struct list_head list_node;
int product_id;
}product_t;
struct list_head productList;// 頭結(jié)點(diǎn)
pthread_mutex_t product_mutex = PTHREAD_MUTEX_INITIALIZER; // productList 的互斥量
sem_t g_sem;
// 生產(chǎn)者線程,1秒生成一個(gè)產(chǎn)品放到鏈表
void *th_producer(void *arg)
{
int id = 0;
while(1)
{
product_t *pProduct = (product_t*)malloc(sizeof(product_t));
pProduct->product_id = id++;
pthread_mutex_lock(&product_mutex);
list_add_tail(&pProduct->list_node, &productList);
pthread_mutex_unlock(&product_mutex);
sem_post(&g_sem);
sleep(1);
}
return NULL;
}
// 消費(fèi)者線程,1秒消耗掉一個(gè)產(chǎn)品
void *th_consumer(void *arg)
{
while(1)
{
pthread_mutex_lock(&product_mutex);
while(list_empty(&productList)) // 條件不滿足
{
pthread_mutex_unlock(&product_mutex);
sem_wait(&g_sem);
pthread_mutex_lock(&product_mutex);
}
// 不為空,則取出一個(gè)
product_t* pProduct = list_entry(productList.next, product_t, list_node);// 獲取第一個(gè)節(jié)點(diǎn)
printf("consumer[%d] get product id=%d\n", *((int*)arg), pProduct->product_id);
list_del(productList.next); // 刪除第一個(gè)節(jié)點(diǎn)
free(pProduct);
pthread_mutex_unlock(&product_mutex);
}
return NULL;
}
int main()
{
INIT_LIST_HEAD(&productList); // 初始化鏈表
sem_init(&g_sem, 0, 1); // 初始化信號(hào)量
// 創(chuàng)建生產(chǎn)者線程
pthread_t producer_thid;
pthread_create(&producer_thid, NULL, th_producer, NULL);
// 創(chuàng)建消費(fèi)者線程
pthread_t consumer_thid[COMSUMER_NUM];
int i=0, num[COMSUMER_NUM]={0,};
for(i=0; i<COMSUMER_NUM; i++)
{
num[i] = i;
pthread_create(&consumer_thid[i], NULL, th_consumer, &num[i]);
}
// 等待線程
pthread_join(producer_thid, NULL);
for(i=0; i<COMSUMER_NUM; i++)
{
pthread_join(consumer_thid[i], NULL);
}
sem_destroy(&g_sem);
return 0;
}
運(yùn)行結(jié)果如下,使生產(chǎn)者線程、消費(fèi)者線程同步訪問(wèn)資源:
??四、計(jì)數(shù)信號(hào)量的使用例子
計(jì)數(shù)信號(hào)量是指初始化時(shí)信號(hào)值大于1的信號(hào)量,它可以與多個(gè)相同的資源關(guān)聯(lián),允許多個(gè)線程并發(fā)的使用多個(gè)資源。在某種程度上來(lái)說(shuō),計(jì)數(shù)信號(hào)量是對(duì)互斥量的一個(gè)擴(kuò)展,互斥量是同一時(shí)間內(nèi)只允許一個(gè)線程訪問(wèn)共享資源,而計(jì)數(shù)信號(hào)量允許多個(gè)線程并發(fā)訪問(wèn)共享資源。
可以用下面這個(gè)例子來(lái)加深理解:
1、互斥量相當(dāng)于只有一個(gè)洗手間和一把鑰匙,要想進(jìn)入這個(gè)洗手間就要先拿到鑰匙,進(jìn)入洗手間,使用完又把鑰匙放回去。
2、計(jì)數(shù)信號(hào)量相當(dāng)于公共衛(wèi)生間里的4個(gè)廁所和4把鑰匙,要想進(jìn)入廁所就先看看還有幾把鑰匙,如果沒(méi)鑰匙了就等待,有鑰匙放出來(lái)就拿鑰匙開鎖進(jìn)入洗手間。
下面以上廁所為例,舉個(gè)計(jì)數(shù)信號(hào)量的例子,8個(gè)線程準(zhǔn)備使用4個(gè)廁所資源,每個(gè)線程上兩次廁所:
// 10_sem_multiple.c
// gcc 10_sem_multiple.c -lpthread
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#define TOILET_NUM 4
#define PEOPLE_NUM 8
int toilets[TOILET_NUM] = {0,}; // 4個(gè)蹲廁
pthread_mutex_t toilet_mutex = PTHREAD_MUTEX_INITIALIZER; // toilets 的互斥量
sem_t g_sem;
int getToilet()
{
int i=0;
for(i=0; i<TOILET_NUM; i++)
{
if(toilets[i] == 0)
break;
}
return i;
}
int sem_value()
{
int semvalue = 0;
sem_getvalue(&g_sem, &semvalue);
return semvalue;
}
// 上廁所線程
void *going_to_the_toilet(void *arg)
{
int id = *((int*)arg);
int count = 2;
while(count-->0){
printf("線程[%d] 等待廁所,廁所數(shù)量=%d\n",id, sem_value());
sem_wait(&g_sem);
pthread_mutex_lock(&toilet_mutex); // 廁所有多個(gè)線程訪問(wèn),加鎖
int i = getToilet();
if(getToilet()==TOILET_NUM){
printf("線程[%d], No toilet\n",id);
}
else{
toilets[i] = 1; // 表示進(jìn)入該廁所
printf("線程[%d] 進(jìn)入廁所[%d], 即將工作 2s\n",id, i);
pthread_mutex_unlock(&toilet_mutex); // 上廁所前先釋放鎖,讓其他人可以訪問(wèn)廁所資源
sleep(2); // 正在上廁所...
pthread_mutex_lock(&toilet_mutex);
toilets[i] = 0;
printf("線程[%d] 完成工作,廁所[%d]空閑\n",id, i);
}
pthread_mutex_unlock(&toilet_mutex);
sem_post(&g_sem);
sleep(1); // 釋放資源后,休眠1秒,確保資源讓出去
}
return NULL;
}
int main()
{
sem_init(&g_sem, 0, TOILET_NUM);// 初始化信號(hào)量值為4
// 創(chuàng)建線程
pthread_t people_thid[PEOPLE_NUM];
int i=0, num[PEOPLE_NUM]={0,};
for(i=0; i<PEOPLE_NUM; i++)
{
num[i] = i;
pthread_create(&people_thid[i], NULL, going_to_the_toilet, &num[i]);
}
// 等待線程
for(i=0; i<PEOPLE_NUM; i++)
{
pthread_join(people_thid[i], NULL);
}
sem_destroy(&g_sem);
return 0;
}
運(yùn)行結(jié)果如下:
??五、總結(jié)
??本文介紹了信號(hào)量的一些基礎(chǔ)知識(shí),然后描述了在多線程編程下使用無(wú)名信號(hào)量的幾個(gè)場(chǎng)景,并給出了使用例子。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-858949.html
如果文章有幫助的話,點(diǎn)贊??、收藏?,支持一波,謝謝 ??????文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-858949.html
到了這里,關(guān)于【Linux C | 多線程編程】線程同步 | 信號(hào)量(無(wú)名信號(hào)量) 及其使用例子的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!