怎樣建設(shè)微網(wǎng)站首頁(yè)網(wǎng)站數(shù)據(jù)統(tǒng)計(jì)工具
Linux系統(tǒng)編程 day09 線程同步
- 1.互斥鎖
- 2.死鎖
- 3.讀寫(xiě)鎖
- 4.條件變量(生產(chǎn)者消費(fèi)者模型)
- 5.信號(hào)量
1.互斥鎖
互斥鎖是一種同步機(jī)制,用于控制多個(gè)線程對(duì)共享資源的訪問(wèn),確保在同一時(shí)間只有一個(gè)線程可以訪問(wèn)特定的資源或執(zhí)行特定的操作。如果沒(méi)有互斥鎖,對(duì)于多個(gè)線程的程序則可能會(huì)出現(xiàn)一些未知的問(wèn)題。比如下面有兩個(gè)線程,一個(gè)是打印“hello world”的線程,一個(gè)是打印“HELLO WORLD”的線程。但是不同的是這個(gè)詞匯是分兩次分別打印的,程序代碼如下。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <pthread.h>
#include <time.h>// 打印 hello world
void *mythread1(void *args)
{while(1){printf("hello ");sleep(rand() % 3);printf("world\n");sleep(rand() % 3);}pthread_exit(NULL);
}// 打印HELLO WORLD
void *mythread2(void *args)
{while(1){printf("HELLO ");sleep(rand() % 3);printf("WORLD\n");sleep(rand() % 3);}pthread_exit(NULL);
}int main()
{int ret = 0;pthread_t thread1, thread2;// 初始化隨機(jī)數(shù)種子srand(time(NULL));ret = pthread_create(&thread1, NULL, mythread1, NULL);if(ret != 0){printf("pthread_create error: [%s]\n", strerror(ret));return -1;}ret = pthread_create(&thread2, NULL, mythread2, NULL);if(ret != 0){printf("pthread_create error: [%s]\n", strerror(ret));return -1;}// 等待線程結(jié)束pthread_join(thread1, NULL);pthread_join(thread2, NULL);return 0;
}
將上述文件命名為01.pthread_lock.c,使用命令make 01.pthread_lock
,則會(huì)自動(dòng)編譯01.pthread_lock.c生成01.pthread_lock,運(yùn)行該程序可以看到如下現(xiàn)象。
可以發(fā)現(xiàn)兩個(gè)進(jìn)程打印出來(lái)的“hello world”和“HELLO WORLD”并不是連續(xù)在一起的,所以需要加互斥鎖將打印的代碼進(jìn)行加鎖。下面是互斥鎖的一些函數(shù)。
/*** @brief 定義一把互斥鎖*/
pthread_mutex_t mutex_var;/** * @brief 初始化互斥鎖* @param mutex 互斥鎖* @param mutexattr 互斥鎖屬性,傳入NULL為默認(rèn)屬性* @return 是否初始化成功,初始化成功返回0,初始化失敗返回錯(cuò)誤碼* @retval int*/
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);/** * @brief 互斥鎖加鎖* @param mutex 互斥鎖* @return 是否加鎖成功,加鎖成功返回0,加鎖失敗會(huì)阻塞在這里,發(fā)生錯(cuò)誤返回錯(cuò)誤碼* @retval int*/
int pthread_mutex_lock(pthread_mutex_t *mutex);/** * @brief 互斥鎖嘗試加鎖* @param mutex 互斥鎖* @return 是否加鎖成功,加鎖成功返回0,加鎖失敗直接返回0,發(fā)生錯(cuò)誤返回錯(cuò)誤碼* @retval int*/
int pthread_mutex_trylock(pthread_mutex_t *mutex);/** * @brief 互斥鎖解鎖* @param mutex 互斥鎖* @return 是否解鎖成功,解鎖成功返回0,解鎖失敗返回錯(cuò)誤碼* @retval int*/
int pthread_mutex_unlock(pthread_mutex_t *mutex);/** * @brief 摧毀互斥鎖* @param mutex 互斥鎖* @return 摧毀成功返回0,摧毀失敗返回錯(cuò)誤碼* @retval int*/
int pthread_mutex_destroy(pthread_mutex_t *mutex);
現(xiàn)在在每個(gè)線程運(yùn)行的函數(shù)中加入互斥鎖,代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <pthread.h>
#include <time.h>// 定義一把互斥鎖
pthread_mutex_t mutex;// 打印 hello world
void *mythread1(void *args)
{while(1){// 加鎖pthread_mutex_lock(&mutex);printf("hello ");sleep(rand() % 3);printf("world\n");sleep(rand() % 3);// 解鎖pthread_mutex_unlock(&mutex);}pthread_exit(NULL);
}// 打印HELLO WORLD
void *mythread2(void *args)
{while(1){// 加鎖pthread_mutex_lock(&mutex);printf("HELLO ");sleep(rand() % 3);printf("WORLD\n");sleep(rand() % 3);// 解鎖pthread_mutex_unlock(&mutex);}pthread_exit(NULL);
}int main()
{int ret = 0;pthread_t thread1, thread2;// 初始化隨機(jī)數(shù)種子srand(time(NULL));// 初始化互斥鎖pthread_mutex_init(&mutex, NULL);ret = pthread_create(&thread1, NULL, mythread1, NULL);if(ret != 0){printf("pthread_create error: [%s]\n", strerror(ret));return -1;}ret = pthread_create(&thread2, NULL, mythread2, NULL);if(ret != 0){printf("pthread_create error: [%s]\n", strerror(ret));return -1;}// 等待線程結(jié)束pthread_join(thread1, NULL);pthread_join(thread2, NULL);// 摧毀互斥鎖pthread_mutex_destroy(&mutex);return 0;
}
編譯后運(yùn)行如下結(jié)果:
可以觀察到這次的運(yùn)行結(jié)果并不會(huì)出現(xiàn)線程間交叉打印的情況。
2.死鎖
死鎖是由程序員對(duì)互斥鎖的使用不當(dāng)而產(chǎn)生的,并不是操作系統(tǒng)提供的一種機(jī)制。死鎖的產(chǎn)生主要由兩種形式。
第一種是自己鎖自己,也就是調(diào)用了兩次加鎖。也就是說(shuō)線程1加鎖了,再?zèng)]釋放鎖的時(shí)候線程1又申請(qǐng)加鎖,此時(shí)就會(huì)產(chǎn)生死鎖,且死鎖阻塞到該位置,還有一種情況是線程1加鎖了,但是線程1的整個(gè)程序里面沒(méi)有釋放鎖的相關(guān)操作或者根本執(zhí)行不到釋放鎖的代碼,此時(shí)若線程2申請(qǐng)加鎖則程序阻塞在此處,若線程1繼續(xù)申請(qǐng)加鎖則阻塞在線程1加鎖處。
第二種是相互鎖住,線程1擁有A鎖請(qǐng)求B鎖,線程2擁有B鎖請(qǐng)求A鎖,這樣就相互阻塞住了。
合理的編寫(xiě)程序能夠避免死鎖,解決司所有一下一些方法:
- 1.讓線程按照一定的順序去訪問(wèn)共享資源。
- 2.在訪問(wèn)其它鎖的時(shí)候先釋放自己的鎖。
- 3.調(diào)用
pthread_mutex_trylock
,該函數(shù)若加鎖不成功會(huì)立刻返回,此時(shí)就不會(huì)造成阻塞死等。
3.讀寫(xiě)鎖
讀寫(xiě)鎖也叫共享獨(dú)占鎖,讀時(shí)共享,寫(xiě)時(shí)獨(dú)占,極大地提高了程序運(yùn)行的效率。也就是當(dāng)讀寫(xiě)鎖以讀模式鎖住的時(shí)候,它是共享的,當(dāng)讀寫(xiě)鎖以寫(xiě)模式鎖住的時(shí)候,是獨(dú)占的。讀寫(xiě)鎖適合的場(chǎng)景是讀的次數(shù)遠(yuǎn)大于寫(xiě)的情況下的。
讀寫(xiě)鎖有以下一些特性:
- 1.讀寫(xiě)鎖是寫(xiě)模式加鎖的時(shí)候,在解鎖前所有要對(duì)該鎖加鎖的線程都會(huì)阻塞。
- 2.讀寫(xiě)鎖是讀模式加鎖的時(shí)候,如果線程以讀模式加鎖則會(huì)成功,以寫(xiě)模式加鎖會(huì)阻塞。
- 3.讀寫(xiě)鎖是讀模式加鎖的時(shí)候,如果既有讀線程也有寫(xiě)線程,則嘗試加鎖都會(huì)被阻塞。若鎖被釋放,則寫(xiě)線程會(huì)請(qǐng)求鎖成功,而讀線程會(huì)繼續(xù)阻塞。也就是讀寫(xiě)都有的時(shí)候優(yōu)先滿足寫(xiě)模式的鎖,也就是寫(xiě)比讀優(yōu)先級(jí)更高。
下面是讀寫(xiě)鎖的接口函數(shù):
/*** @brief 定義讀寫(xiě)鎖變量*/
pthread_rwlock_t rwlock_var;/*** @brief 讀寫(xiě)鎖初始化* * 初始化讀寫(xiě)鎖 * @param rwlock 讀寫(xiě)鎖* @param attr 讀寫(xiě)鎖屬性,傳入NULL為默認(rèn)屬性* @return 初始化成功返回0,反之返回錯(cuò)誤碼* @retval int*/
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);/*** @brief 讀寫(xiě)鎖加讀鎖* @param rwlock 讀寫(xiě)鎖* @return 成功加鎖返回0,失敗則返回錯(cuò)誤碼,鎖被占用則一直阻塞* @retval int*/
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);/*** @brief 讀寫(xiě)鎖嘗試加讀鎖* @param rwlock 讀寫(xiě)鎖* @return 成功加鎖返回0,失敗則返回錯(cuò)誤碼,鎖被占用也會(huì)直接返回* @retval int*/
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);/*** @brief 讀寫(xiě)鎖加寫(xiě)鎖* @param rwlock 讀寫(xiě)鎖* @return 成功加鎖返回0,失敗則返回錯(cuò)誤碼,鎖被占用則一直阻塞* @retval int*/
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);/*** @brief 讀寫(xiě)鎖嘗試加寫(xiě)鎖* @param rwlock 讀寫(xiě)鎖* @return 成功加鎖返回0,失敗則返回錯(cuò)誤碼,鎖被占用也會(huì)直接返回* @retval int*/
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);/*** @brief 讀寫(xiě)鎖解鎖* @param rwlock 讀寫(xiě)鎖* @return 成功解鎖返回0,失敗則返回錯(cuò)誤碼* @retval int*/
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);/*** @brief 摧毀讀寫(xiě)鎖* @param rwlock 讀寫(xiě)鎖* @return 成功摧毀返回0,失敗則返回錯(cuò)誤碼* @retval int*/
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
下面舉一個(gè)關(guān)于讀寫(xiě)鎖的例子,有3個(gè)寫(xiě)線程,5個(gè)讀線程,寫(xiě)線程修改數(shù)字,讀線程讀取數(shù)字輸出到終端。代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <pthread.h>// 線程總數(shù)
#define THREAD_COUNT 8// 定義讀寫(xiě)鎖
pthread_rwlock_t rwlock;// 計(jì)數(shù)
int number = 0;// 讀線程回調(diào)函數(shù)
void *thread_read(void *args)
{int i = *(int *)args;int cur;while(1){// 讀寫(xiě)鎖加讀鎖pthread_rwlock_rdlock(&rwlock);cur = number;printf("[%d]-R: [%d]\n", i, cur);// 讀寫(xiě)鎖加寫(xiě)鎖pthread_rwlock_unlock(&rwlock);sleep(rand() % 3);}
}// 寫(xiě)線程回調(diào)函數(shù)
void *thread_write(void *args)
{int i = *(int *)args;int cur;while(1){// 讀寫(xiě)鎖加寫(xiě)鎖pthread_rwlock_wrlock(&rwlock);cur = number;cur += 1;number = cur;printf("[%d]-W: [%d]\n", i, cur);// 讀寫(xiě)鎖解鎖pthread_rwlock_unlock(&rwlock);sleep(rand() % 3);}
}int main()
{int ret = 0;int arr[THREAD_COUNT]; // 記錄第幾個(gè)線程pthread_t threads[THREAD_COUNT]; // 線程數(shù)組// 初始化隨機(jī)數(shù)種子srand(time(NULL));// 初始化讀寫(xiě)鎖pthread_rwlock_init(&rwlock, NULL);// 創(chuàng)建3個(gè)寫(xiě)線程for(int i = 0; i < 3; i++){arr[i] = i;ret = pthread_create(&threads[i], NULL, thread_write, &arr[i]);if(ret != 0){printf("pthread_create error: [%s]\n", strerror(ret));return -1;}}// 創(chuàng)建5個(gè)讀線程for(int i = 3; i < THREAD_COUNT; i++){arr[i] = i;ret = pthread_create(&threads[i], NULL, thread_read, &arr[i]);if(ret != 0){printf("pthread_create error: [%s]\n", strerror(ret));return -1;}}// 回收子線程for(int i = 0; i < THREAD_COUNT; i++){pthread_join(threads[i], NULL);}// 釋放鎖pthread_rwlock_destroy(&rwlock);return 0;
}
編譯程序運(yùn)行的結(jié)果如下:
可以發(fā)現(xiàn)讀線程輸出的數(shù)字都是寫(xiě)線程修改后的,并不會(huì)出現(xiàn)讀線程的數(shù)據(jù)是不符合寫(xiě)進(jìn)程修改后的。如果沒(méi)有加讀寫(xiě)鎖,則會(huì)讓讀寫(xiě)亂套,當(dāng)寫(xiě)線程修改完數(shù)據(jù)之后讀線程也可能正好輸出修改前的值。
4.條件變量(生產(chǎn)者消費(fèi)者模型)
條件變量主要是用于生產(chǎn)者消費(fèi)者模型的。所謂生產(chǎn)者消費(fèi)者模型就是生產(chǎn)者負(fù)責(zé)生成東西,而消費(fèi)者負(fù)責(zé)對(duì)生產(chǎn)者生產(chǎn)者的東西用于消費(fèi)。在這種情況下,生產(chǎn)者消費(fèi)的東西其實(shí)也就是臨界資源,所以需要使用互斥鎖進(jìn)行訪問(wèn)。而在生產(chǎn)者消費(fèi)者模型中無(wú)法確定是生產(chǎn)者先訪問(wèn)還是消費(fèi)者先訪問(wèn),所以在生產(chǎn)者沒(méi)有生產(chǎn)東西的時(shí)候消費(fèi)者也可能需要進(jìn)行消費(fèi),或者生產(chǎn)者生產(chǎn)的進(jìn)度會(huì)更不上消費(fèi)者消耗的進(jìn)度。因此,為了保證消費(fèi)者每次消費(fèi)都是在生產(chǎn)者生成東西之后或者生產(chǎn)的東西有剩余,所以需要使用到條件變量,此時(shí)原來(lái)并行的生產(chǎn)者消費(fèi)者就變成了串行的生產(chǎn)者消費(fèi)者。關(guān)于條件變量的接口如下:
/*** @brief 條件變量定義,定義一個(gè)條件變量*/
pthread_cond_t cond_var;/*** @brief 初始化條件變量* @param cond 條件變量* @param cond_attr 條件變量屬性,傳入NULL為默認(rèn)屬性* @return 初始化成功返回0,失敗返回錯(cuò)誤碼* @retval int*/
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);/*** @brief 喚醒至少一個(gè)阻塞在該條件變量上的線程* @param cond 條件變量* @return 喚醒成功返回0,失敗返回0* @retval int*/
int pthread_cond_signal(pthread_cond_t *cond);/*** @brief 喚醒所有阻塞在該條件變量上的線程* @param cond 條件變量* @return 喚醒成功返回0,失敗返回0* @retval int*/
int pthread_cond_broadcast(pthread_cond_t *cond);/*** @brief 阻塞等待條件變量滿足* * 當(dāng)條件變量不滿足的時(shí)候,會(huì)阻塞線程并進(jìn)行解鎖* 當(dāng)條件變量滿足的時(shí)候,會(huì)解除線程阻塞并加鎖* @param cond 條件變量* @param mutex 互斥鎖* @return 成功返回0,失敗返回0* @retval int*/
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);/*** @brief 銷(xiāo)毀條件變量* @param cond 條件變量* @return 銷(xiāo)毀成功返回0,失敗返回0* @retval int*/
int pthread_cond_destroy(pthread_cond_t *cond);
需要注意的是條件變量本身并不是鎖,但是它可以造成線程的阻塞,所以通常需要與互斥鎖配合使用。使用互斥鎖是為了保護(hù)共享數(shù)據(jù),使用條件變量可以使線程阻塞,等待某個(gè)條件的發(fā)生,當(dāng)條件滿足的時(shí)候解除阻塞。
下面給一個(gè)關(guān)于生產(chǎn)者消費(fèi)者的例子,有一個(gè)鏈表,生產(chǎn)者負(fù)責(zé)不斷地從堆區(qū)申請(qǐng)空間開(kāi)辟內(nèi)存并且存入數(shù)據(jù)并將節(jié)點(diǎn)放到鏈表上,而消費(fèi)者負(fù)責(zé)在鏈表上不斷地讀取數(shù)據(jù)并且釋放節(jié)點(diǎn),代碼如下所示。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>// 鏈表節(jié)點(diǎn)定義
typedef struct node
{int data;struct node *next;
} node_t;// 互斥鎖
pthread_mutex_t mutex;
// 條件變量
pthread_cond_t cond;
// 頭結(jié)點(diǎn)
node_t *head = NULL;// 生產(chǎn)者線程
void *thread_product(void *args)
{while(1){// 申請(qǐng)節(jié)點(diǎn)node_t *pnode = (node_t *)malloc(sizeof(node_t));if(pnode == NULL){perror("malloc error");exit(-1);}pnode->data = rand() % 1000;// 加互斥鎖pthread_mutex_lock(&mutex);pnode->next = head;head = pnode;printf("[Productor]: %d\n", pnode->data);// 解鎖pthread_mutex_unlock(&mutex);// 通知消費(fèi)者線程pthread_cond_signal(&cond);sleep(rand() % 3);}pthread_exit(NULL);
}// 消費(fèi)者線程
void *thread_consume(void *args)
{while(1){// 加互斥鎖pthread_mutex_lock(&mutex);if(head == NULL){// 條件滿足解除阻塞并加鎖// 條件不滿足阻塞并解鎖pthread_cond_wait(&cond, &mutex);}node_t *pnode = head;head = pnode->next;printf("[Consumer]: %d\n", pnode->data);free(pnode);pnode = NULL;// 解鎖pthread_mutex_unlock(&mutex);sleep(rand() % 3);}pthread_exit(NULL);
}int main()
{int ret;// 生產(chǎn)者與消費(fèi)者線程pthread_t thread_productor, thread_consumer;// 初始化隨機(jī)數(shù)種子srand(time(NULL));// 初始化條件變量與互斥鎖pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond, NULL);ret = pthread_create(&thread_productor, NULL, thread_product, NULL);if(ret != 0){printf("pthread_create error: [%s]\n", strerror(ret));return -1;}ret = pthread_create(&thread_consumer, NULL, thread_consume, NULL);if(ret != 0){printf("pthread_create error: [%s]\n", strerror(ret));return -1;}// 回收線程pthread_join(thread_productor, NULL);pthread_join(thread_consumer, NULL);// 銷(xiāo)毀互斥鎖與條件變量pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}
運(yùn)行可以發(fā)現(xiàn)消費(fèi)者消費(fèi)的是生產(chǎn)者最后一次生產(chǎn)出來(lái)的。
如果將上述的代碼改為多個(gè)消費(fèi)者線程和多個(gè)生產(chǎn)者線程的時(shí)候,則上述的代碼就會(huì)出現(xiàn)Segmentation fault (core dumped)
的情況。首先需要分析這個(gè)問(wèn)題是如何產(chǎn)生的。這個(gè)問(wèn)題的原因就是訪問(wèn)了非法的空間內(nèi)存,而這種情況只會(huì)在消費(fèi)者線程中出現(xiàn)。而經(jīng)過(guò)定位可以發(fā)現(xiàn)對(duì)NULL
指針進(jìn)行了操作,也就是head = pnode->next; printf("[Consumer]: %d\n", pnode->data);
。首先在生產(chǎn)者線程中調(diào)用了pthread_cond_signal
函數(shù)的時(shí)候會(huì)通知一個(gè)或者多個(gè)消費(fèi)者線程喚醒滿足,而此時(shí)這幾個(gè)線程都不在處于阻塞狀態(tài),就會(huì)進(jìn)行解鎖,當(dāng)連續(xù)多個(gè)消費(fèi)者線程依次解鎖的時(shí)候向下運(yùn)行就會(huì)導(dǎo)致訪問(wèn)到非法空間。比如鏈表中只有一個(gè)節(jié)點(diǎn),而此時(shí)有三個(gè)消費(fèi)者線程處于阻塞狀態(tài)且阻塞在pthread_cond_wait
處。此時(shí)生產(chǎn)者線程調(diào)用了pthread_cond_signal
則會(huì)喚醒這消費(fèi)者三個(gè)線程的一個(gè)或者是兩個(gè),又或者是三個(gè)。此時(shí)三個(gè)的條件變量都滿足,第一個(gè)消費(fèi)者線程搶到了鎖消費(fèi)了一個(gè)結(jié)點(diǎn)。而此時(shí)生產(chǎn)者線程沒(méi)有創(chuàng)造新的節(jié)點(diǎn),由于第二個(gè)線程已經(jīng)滿足了條件變量,此時(shí)它搶到了互斥鎖,則也會(huì)向下執(zhí)行,由于前面的線程已經(jīng)消費(fèi)了唯一的一個(gè)線程,所以此時(shí)就會(huì)訪問(wèn)到非法的內(nèi)存空間。應(yīng)對(duì)這種情況則是在pthread_cond_wait
后面對(duì)頭結(jié)點(diǎn)再一次進(jìn)行判斷,若滿足條件則向下執(zhí)行,不滿足條件則解鎖并使用continue
進(jìn)入下一次循環(huán)。以5個(gè)生產(chǎn)者線程10個(gè)消費(fèi)者線程為例,代碼如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <string.h>// 線程數(shù)量
#define PRODUCER_THREAD_COUNT 5 // 生產(chǎn)線程數(shù)量
#define CONSUMER_THREAD_COUNT 10 // 消費(fèi)線程數(shù)量// 鏈表節(jié)點(diǎn)
struct node
{int data;struct node *next;
};// 頭結(jié)點(diǎn)
struct node *head = NULL;// 互斥鎖
pthread_mutex_t mutex;// 條件變量
pthread_cond_t cond;void *thread_producer(void *arg)
{struct node *pnode = NULL;int idx = (int)(long)arg;while(1){pnode = (struct node *)malloc(sizeof(struct node));if(pnode == NULL){perror("malloc error");exit(-1);}pnode->data = rand() % 100;// 加鎖pthread_mutex_lock(&mutex);pnode->next = head;head = pnode;printf("P[%d]: %d\n", idx, pnode->data);pthread_mutex_unlock(&mutex);pthread_cond_signal(&cond);sleep(rand() % 3);}pthread_exit(NULL);
}void *thread_consumer(void *arg)
{struct node *pnode = NULL;int idx = (int)(long)arg;while(1){ pthread_mutex_lock(&mutex);if(head == NULL){pthread_cond_wait(&cond, &mutex);}// 若頭節(jié)點(diǎn)為空if(head == NULL){// 解鎖pthread_mutex_unlock(&mutex);continue;}pnode = head;head = head->next;printf("C[%d]: %d\n", idx, pnode->data);pthread_mutex_unlock(&mutex);free(pnode);pnode = NULL;sleep(rand() % 3);}pthread_exit(NULL);
}int main()
{int ret = 0;pthread_t producer_thread[PRODUCER_THREAD_COUNT], consumer_thread[CONSUMER_THREAD_COUNT];srand((unsigned int)time(NULL));// 初始化互斥鎖pthread_mutex_init(&mutex, NULL);// 初始化條件變量pthread_cond_init(&cond, NULL);// 創(chuàng)建生產(chǎn)者線程for(int i = 1; i <= PRODUCER_THREAD_COUNT; i++){ret = pthread_create(&producer_thread[i - 1], NULL, thread_producer, (void *)(long)i);if(ret != 0){printf("pthread_create error: %s\n", strerror(ret));exit(-1);}}// 創(chuàng)建消費(fèi)者線程for(int i = 1; i <= CONSUMER_THREAD_COUNT; i++){ ret = pthread_create(&consumer_thread[i - 1], NULL, thread_consumer, (void *)(long)i);if(ret != 0){printf("pthread_create error: %s\n", strerror(ret));exit(-1);}}// 回收線程for(int i = 0; i < PRODUCER_THREAD_COUNT; i++){pthread_join(producer_thread[i], NULL);}for(int i = 0; i < CONSUMER_THREAD_COUNT; i++){pthread_join(consumer_thread[i], NULL);}// 摧毀互斥鎖pthread_mutex_destroy(&mutex);return 0;
}
運(yùn)行結(jié)果如下:
5.信號(hào)量
信號(hào)量可以理解為是升級(jí)版的互斥鎖,因?yàn)榛コ怄i只能運(yùn)行一個(gè)線程進(jìn)行訪問(wèn),而信號(hào)量可以支持多個(gè)線程或者進(jìn)程。關(guān)于信號(hào)量的接口如下所示:
/*** @brief 定義信號(hào)量*/
sem_t sem_var;/*** @brief 初始化信號(hào)量* @param sem 信號(hào)量* @param pshared 0表示線程同步,1表示進(jìn)程同步* @param value 最多有多少個(gè)線程操作共享數(shù)據(jù)* @return 成功返回0,失敗返回-1,并設(shè)置errno的值。* @retval int*/
int sem_init(sem_t *sem, int pshared, unsigned int value);/*** @brief 相當(dāng)于sem--,當(dāng)sem為0的時(shí)候,引起阻塞,即加鎖。* @param sem 信號(hào)量* @return 成功返回0,失敗返回-1,并設(shè)置error的值* @retval int*/
int sem_wait(sem_t *sem);/*** @brief 與sem_wait一樣,但是不會(huì)阻塞* @param sem 信號(hào)量* @return 成功返回0,失敗返回-1,并設(shè)置error的值* @retval int*/
int sem_trywait(sem_t *sem);/*** @brief 相當(dāng)于sem++* @param sem 信號(hào)量* @return 成功返回0,失敗返回-1,并設(shè)置errno的值* @retval int*/
int sem_post(sem_t *sem);/*** @brief 摧毀信號(hào)量* @param sem 信號(hào)量* @return 成功返回0,失敗返回-1,并設(shè)置errno的值*/
int sem_destroy(sem_t *sem);
什么時(shí)候用信號(hào)量呢?也就是當(dāng)一個(gè)共享資源能夠被多個(gè)現(xiàn)成進(jìn)行操作的時(shí)候。比如停車(chē)場(chǎng),里面會(huì)有很多個(gè)車(chē)位,可以同時(shí)允許多個(gè)車(chē)位進(jìn)行???#xff0c;代碼如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>// 車(chē)位數(shù)量
#define PARKING_COUNT 10// 線程數(shù)量
#define ENTRY_PARKING_THREAD_COUNT 10 // 進(jìn)入停車(chē)場(chǎng)線程
#define EXIT_PARKING_THREAD_COUNT 10 // 離開(kāi)停車(chē)場(chǎng)線程// 結(jié)點(diǎn)模擬停車(chē)場(chǎng)
struct park
{int use; // 是否被使用int parking_space; // 停車(chē)位編號(hào)int license_plate_number; // 停車(chē)車(chē)牌號(hào)
}park[PARKING_COUNT];// 信號(hào)量
sem_t entry_sem; // 進(jìn)入停車(chē)場(chǎng)信號(hào)量
sem_t exit_sem; // 離開(kāi)停車(chē)場(chǎng)信號(hào)量// 進(jìn)入停車(chē)場(chǎng)線程
void *thread_entryParking(void *arg)
{// 標(biāo)記是否停車(chē)int parking_space = -1;int license_plate_number = 0;while(1){// 車(chē)位標(biāo)志parking_space = 0;license_plate_number = rand() % 10000 + 10000;printf("車(chē)牌號(hào)[%d]駛來(lái)了\n", license_plate_number);sleep(rand() % 5);// 開(kāi)始停車(chē)sem_wait(&entry_sem);// 尋找車(chē)位for(int i = 0; i < PARKING_COUNT; i++){if(park[i].use == 0){parking_space = park[i].parking_space;break;}}// 沒(méi)有找到停車(chē)位if(parking_space == 0){sem_post(&entry_sem);printf("車(chē)牌號(hào)[%d]因?yàn)闆](méi)有車(chē)位離開(kāi)了\n", license_plate_number);continue;}// 開(kāi)始停車(chē)park[parking_space - 1].license_plate_number = license_plate_number;park[parking_space - 1].use = 1;printf("車(chē)牌號(hào)[%d]停進(jìn)了停車(chē)場(chǎng)的[%d]車(chē)位\n", license_plate_number, park[parking_space - 1].parking_space);// 通知可以有車(chē)離開(kāi)sem_post(&exit_sem);}pthread_exit(NULL);
}// 離開(kāi)停車(chē)場(chǎng)
void *thread_exitParking(void *arg)
{// 車(chē)位int parking_space = 0;while(1){sleep(rand() % 5);// 隨機(jī)車(chē)位parking_space = rand() % PARKING_COUNT + 1;sem_wait(&exit_sem);// 該車(chē)位沒(méi)有車(chē)?yán)^續(xù)下一次if(park[parking_space - 1].use == 0){sem_post(&exit_sem);continue;}printf("車(chē)牌號(hào)[%d]離開(kāi)了停車(chē)場(chǎng)的[%d]車(chē)位\n", park[parking_space - 1].license_plate_number, park[parking_space - 1].parking_space);// 標(biāo)記車(chē)位沒(méi)有使用park[parking_space - 1].use = 0;// 可停車(chē)數(shù)加1sem_post(&entry_sem);}pthread_exit(NULL);
}int main()
{int ret = 0;// 進(jìn)入停車(chē)場(chǎng)線程pthread_t entryParking_thread[ENTRY_PARKING_THREAD_COUNT];// 離開(kāi)停車(chē)場(chǎng)線程pthread_t exitParking_thread[EXIT_PARKING_THREAD_COUNT];srand((unsigned int)time(NULL));// 初始化信號(hào)量sem_init(&entry_sem, 0, PARKING_COUNT);sem_init(&exit_sem, 0, 0);// 初始化停車(chē)場(chǎng)memset(&park, 0x00, sizeof park);for(int i = 0; i < PARKING_COUNT; i++){park[i].parking_space = i + 1;}// 創(chuàng)建停車(chē)線程for(int i = 0; i < ENTRY_PARKING_THREAD_COUNT; i++){ret = pthread_create(&entryParking_thread[i], NULL, thread_entryParking, NULL);if(ret != 0){printf("pthread_create error: %s\n", strerror(ret));exit(-1);}}// 創(chuàng)建離開(kāi)停車(chē)場(chǎng)線程for(int i = 0; i < EXIT_PARKING_THREAD_COUNT; i++){ret = pthread_create(&exitParking_thread[i], NULL, thread_exitParking, NULL);if(ret != 0){printf("pthread_create error: %s\n", strerror(ret));exit(-1);}}// 回收線程for(int i = 0; i < ENTRY_PARKING_THREAD_COUNT; i++){pthread_join(entryParking_thread[i], NULL);}for(int i = 0; i < EXIT_PARKING_THREAD_COUNT; i++){pthread_join(exitParking_thread[i], NULL);}// 摧毀信號(hào)量sem_destroy(&entry_sem);sem_destroy(&exit_sem);return 0;
}
運(yùn)行上述代碼,結(jié)果如下: