參考文章:https://blog.csdn.net/Alkaid2000/article/details/128121066
一、線程同步
-
線程的主要優勢在于,能夠通過全局變量來共享信息。不過,這種便攜的共享是有代價的;必須確保多個線程不會同時修改同一變量,或者某一線程不會讀取正在由其他線程修改的變量。(否則會出現數據安全現象)
-
臨界區是指訪問某一共享資源的代碼片段,并且這段代碼的執行應為原子操作,也就是同時訪問同一共享資源的其他線程不應中斷該片段的執行。
-
線程同步:即當有一個線程在對內存進行操作時,其他線程都不可以對這個內存地址進行操作,直到線程完成操作,其他線程才能對內存地址進行操作,而其他線程則處于等待狀態,肯定會影響效率,但是起碼他只是影響了代碼的一小部分。
二、互斥鎖
-
為避免線程更新共享變量時出現問題,可以使用互斥量(mutex——mutual exclusion)來確保同時僅有一個線程可以訪問某項共享資源。可以使用互斥量來保證對任意共享資源的原子訪問。
-
互斥量有兩種狀態:已鎖定(locked)和未鎖定(unlocked)。任何時候,至多只有一個線程可以鎖定該互斥量。試圖對已經鎖定的某一互斥量再次加鎖,將可能阻塞線程或報錯失敗,具體取決于加鎖時使用的方法。
-
一旦線程鎖定互斥量,隨即稱為該互斥量的所有者,只有所有者才能給互斥量解鎖。一般情況下,對每一共享資源(可能由多個相關變量組成)會使用不同的互斥量,每一線程在訪問同一資源時將采用如下協議:
- 針對共享資源鎖定互斥量。
- 訪問共享資源。
- 對互斥量解鎖。
-
如果多個線程試圖執行這一塊代碼(一個臨界區),事實上只有一個線程能夠持有該互斥量(其他線程將遭到阻塞),即同時只有一個線程能夠進入這段代碼區域。
- 關于互斥量有如下的操作函數,與線程屬性相似,它具有一個互斥量類型pthread_mutex_t
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);int pthread_mutex_destroy(pthread_mutex_t *mutex);int pthread_mutex_lock(pthread_mutex_t *mutex);int pthread_mutex_trylock(pthread_mutex_t *mutex);int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 下面的代碼模擬多線程環境下的賣票場景,通過互斥鎖保證多個線程安全訪問共享資源(剩余票數),避免競態條件。
size_t tickets = 100;
pthread_mutex_t mutex;void *sellTicket(void *arg)
{while (1){pthread_mutex_lock(&mutex);if (tickets > 0){std::cout << "the rest of tickets are " << --tickets << " thread ID:" << pthread_self() << std::endl;usleep(1000);}else{pthread_mutex_unlock(&mutex);break;}pthread_mutex_unlock(&mutex);}return NULL;
}void test1()
{pthread_t t1, t2, t3;pthread_mutex_init(&mutex, NULL);pthread_create(&t1, NULL, sellTicket, NULL);pthread_create(&t2, NULL, sellTicket, NULL);pthread_create(&t3, NULL, sellTicket, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_mutex_destroy(&mutex);
}
2.2 核心代碼分析
size_t tickets = 100;
pthread_mutex_t mutex;void *sellTicket(void *arg) {while (1) {pthread_mutex_lock(&mutex); // 加鎖,進入臨界區if (tickets > 0) {std::cout << "剩余票數: " << --tickets << " 線程ID: " << pthread_self() << std::endl;usleep(1000); // 模擬售票耗時} else {pthread_mutex_unlock(&mutex); // 解鎖,退出臨界區break;}pthread_mutex_unlock(&mutex); // 解鎖,退出臨界區}return NULL;
}
2.3 關鍵API詳解:互斥鎖(pthread_mutex_t
)
2.3.1 初始化函數
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr
);
- 參數:
mutex
:互斥鎖變量指針,用于存儲鎖的狀態。attr
:互斥鎖屬性,NULL表示使用默認屬性。- 常見屬性:
PTHREAD_MUTEX_NORMAL
:普通鎖,不檢測死鎖。PTHREAD_MUTEX_ERRORCHECK
:錯誤檢查鎖,檢測死鎖并返回錯誤。PTHREAD_MUTEX_RECURSIVE
:遞歸鎖,允許同一線程多次加鎖。
- 常見屬性:
- 返回值:
- 成功:返回0。
- 失敗:返回錯誤碼(如
EAGAIN
資源不足,ENOMEM
內存分配失敗)。
2.3.2 加鎖函數
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime
);
- 參數:
mutex
:互斥鎖指針。abstime
:超時時間(絕對時間,如CLOCK_REALTIME
)。
- 返回值:
pthread_mutex_lock
:成功返回0,失敗返回錯誤碼(如EDEADLK
檢測到死鎖)。pthread_mutex_trylock
:鎖可用時返回0,不可用時返回EBUSY
(不阻塞)。pthread_mutex_timedlock
:超時返回ETIMEDOUT
,其他同lock
。
2.3.3 解鎖函數
int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 參數:
mutex
:互斥鎖指針。
- 返回值:
- 成功:返回0。
- 失敗:返回錯誤碼(如
EPERM
當前線程未持有鎖)。
2.3.4 銷毀函數
int pthread_mutex_destroy(pthread_mutex_t *mutex);
- 注意:
- 必須在鎖未被持有時調用。
- 銷毀后不可再使用,需重新初始化。
2.3.5 運行結果
死鎖
-
在使用互斥量的時候,會可能出現一些問題,這些問題就是叫死鎖。
-
有時候,一個線程需要同時訪問兩個或更多不同的共享資源,就好比我們有兩個共享資源AB,如果我們只加一個互斥量,同時鎖住AB,如果AB離得近還好說,但是如果它中間隔著有一大段代碼,那還是再考慮加個鎖吧。所以就有了每個資源又都由不同的互斥量管理,當超過一個線程加鎖同一組互斥量時,就有可能發生死鎖。
-
兩個或兩個以上的進程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,他們都無法推進下去,此時稱系統處于死鎖狀態或系統產生了死鎖。
死鎖的幾種場景:
- 忘記釋放鎖
- 重復加鎖(一個線程加了兩道鎖,兩個鎖都是一樣的鎖)
- 多線程多鎖,搶占鎖資源
下面是死鎖的一個案例代碼
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>// 創建2個互斥量
pthread_mutex_t mutex1, mutex2;void * workA(void * arg) {pthread_mutex_lock(&mutex1);sleep(1);pthread_mutex_lock(&mutex2);printf("workA....\n");// 解鎖順序需要相反pthread_mutex_unlock(&mutex2);pthread_mutex_unlock(&mutex1);return NULL;
}void * workB(void * arg) {//注意這里加鎖的順序與A是相反的,所以它首先拿到了另外一個鎖pthread_mutex_lock(&mutex2);sleep(1);pthread_mutex_lock(&mutex1);printf("workB....\n");pthread_mutex_unlock(&mutex1);pthread_mutex_unlock(&mutex2);return NULL;
}int main() {// 初始化互斥量pthread_mutex_init(&mutex1, NULL);pthread_mutex_init(&mutex2, NULL);// 創建2個子線程pthread_t tid1, tid2;pthread_create(&tid1, NULL, workA, NULL);pthread_create(&tid2, NULL, workB, NULL);// 回收子線程資源pthread_join(tid1, NULL);pthread_join(tid2, NULL);// 釋放互斥量資源pthread_mutex_destroy(&mutex1);pthread_mutex_destroy(&mutex2);return 0;
}
如何避免死鎖
當兩個線程為了保護兩個不同的共享資源而使用了兩個互斥鎖,那么這兩個互斥鎖應用不當的時候,可能會造成兩個線程都在等待對方釋放鎖,在沒有外力的作用下,這些線程會一直相互等待,就沒辦法繼續運行,這種情況就是發生了死鎖,那么死鎖只有同時滿足以下四個條件才會發生:
- 互斥條件。
- 持有并等待條件。
- 不可剝奪條件。
- 環路等待條件。
互斥條件:對于互斥條件是指多個線程不能同時同一個資源:如果線程 A 已經持有的資源,不能再同時被線程 B 持有,如果線程 B 請求獲取線程 A 已經占用的資源,那線程 B 只能等待,直到線程 A 釋放了資源。
持有并等待條件:持有并等待條件是指,當線程 A 已經持有了資源 1,又想申請資源 2,而資源 2 已經被線程 C 持有了,所以線程 A 就會處于等待狀態,但是線程 A 在等待資源 2 的同時并不會釋放自己已經持有的資源 1。
不可剝奪條件:不可剝奪條件是指,當線程已經持有了資源 ,在自己使用完之前不能被其他線程獲取,線程 B 如果也想使用此資源,則只能在線程 A 使用完并釋放后才能獲取。
環路等待條件:環路等待條件指的是,在死鎖發生的時候,兩個線程獲取資源的順序構成了環形鏈。比如,線程 A 已經持有資源 2,而想請求資源 1, 線程 B 已經獲取了資源 1,而想請求資源 2,這就形成資源請求等待的環形圖。也就是上述那個代碼的情況。
-
那么避免死鎖的問題就需要破壞其中一個條件即可,那么最常見的方法且可行的就是使用資源有序分配法,來破壞環路等待條件。
-
線程 A 和 線程 B 獲取資源的順序要一樣,當線程 A 是先嘗試獲取資源 A,然后嘗試獲取資源 B 的時候,線程 B 同樣也是先嘗試獲取資源 A,然后嘗試獲取資源 B。也就是說,線程 A 和 線程 B 總是以相同的順序申請自己想要的資源。這樣就可以打破死鎖了:
三、讀寫鎖
-
當有一個線程已經持有互斥鎖時,互斥鎖將所有試圖進入臨界區的線程都阻塞住。但是考慮一種情形,當前持有互斥鎖的線程只是要讀訪問共享資源,而同時有其他幾個線程也想讀取這個共享資源,但是由于互斥鎖的排他性,所有其他線程都無法獲取鎖,也就無法讀訪問共享資源了,但是實際上多個線程同時讀訪問共享資源并不會導致問題。
-
在對數據的讀寫操作中,更多的是讀操作,寫操作較少,例如對數據庫數據的讀寫應用。為了滿足當前能夠允許多個讀出,但只允許一個寫入的需求,線程提供了讀寫鎖來實現。
讀寫鎖的特點:
- 如果有其他線程讀數據,則允許其他線程執行讀操作,但不允許寫操作。
- 如果有其他線程寫數據,則其他線程都不允許讀、寫操作。
- 寫是獨占的,寫的優先級高。
那么關于讀寫鎖相關的操作函數,有如下,當然,這里也有一個結構體,表達了讀寫鎖的屬性pthread_rwlock_t
,這里也是一把鎖,只不過是可以設置為讀或者是寫:
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock)int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
3.1 功能描述
使用讀寫鎖實現“多讀單寫”場景:多個讀線程可并發訪問資源,寫線程獨占資源,提升讀多寫少場景的性能。
pthread_rwlock_t rwLock;size_t resource = 0;
void *readTask(void *arg)
{for (int i = 0; i < 100; ++i){pthread_rwlock_rdlock(&rwLock);std::cout << "----------Read Task----------" << std::endl;std::cout << "resouce = " << resource << " thread ID = " << pthread_self() << std::endl;pthread_rwlock_unlock(&rwLock);usleep(2000);}return NULL;
}void *writeTask(void *arg)
{for (int i = 0; i < 100; ++i){pthread_rwlock_wrlock(&rwLock);resource++;std::cout << "----------Write Task----------" << std::endl;std::cout << "resouce = " << resource << " thread ID = " << pthread_self() << std::endl;pthread_rwlock_unlock(&rwLock);usleep(1000);}return NULL;
}void test2()
{pthread_t t1, t2, t3;pthread_rwlock_init(&rwLock, NULL);pthread_create(&t1, NULL, readTask, NULL);pthread_create(&t2, NULL, readTask, NULL);pthread_create(&t3, NULL, writeTask, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_rwlock_destroy(&rwLock);
}
3.2 核心代碼分析
pthread_rwlock_t rwLock;
size_t resource = 0;void *readTask(void *arg) {for (int i = 0; i < 100; ++i) {pthread_rwlock_rdlock(&rwLock); // 加讀鎖std::cout << "讀取資源: " << resource << " 線程ID: " << pthread_self() << std::endl;pthread_rwlock_unlock(&rwLock); // 解鎖usleep(2000);}return NULL;
}void *writeTask(void *arg) {for (int i = 0; i < 100; ++i) {pthread_rwlock_wrlock(&rwLock); // 加寫鎖resource++;std::cout << "寫入資源: " << resource << " 線程ID: " << pthread_self() << std::endl;pthread_rwlock_unlock(&rwLock); // 解鎖usleep(1000);}return NULL;
}
3.3 關鍵API詳解:讀寫鎖(pthread_rwlock_t
)
3.3.1 初始化與銷毀
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr
);int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
- 參數:
attr
:讀寫鎖屬性,NULL表示默認屬性。- 常見屬性:
PTHREAD_RWLOCK_PREFER_READER_NP
:默認,讀者優先(可能導致寫者饑餓)。PTHREAD_RWLOCK_PREFER_WRITER_NP
:寫者優先(高并發下可能降低性能)。
- 常見屬性:
3.3.2 加讀鎖函數
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock,const struct timespec *restrict abstime
);
- 返回值:
pthread_rwlock_rdlock
:成功返回0,失敗返回錯誤碼。pthread_rwlock_tryrdlock
:鎖不可用時返回EBUSY
(不阻塞)。
3.3.3 加寫鎖函數
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock,const struct timespec *restrict abstime
);
- 返回值:
- 同讀鎖函數,但寫鎖需等待所有讀鎖釋放。
3.3.4 解鎖函數
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
- 注意:
- 讀鎖和寫鎖使用相同的解鎖函數。
- 必須由持有鎖的線程調用。
3.3.5 運行結果
四、生產者-消費者模型
一、生產者-消費者模型概念
假設有兩個進程(或線程)A、B和一個固定大小的緩沖區,A進程產生的數據放入緩沖區,B進程從緩沖區中取出數據進行計算,這就是一個簡單的生產者和消費者模型。這里的A相當于生產者,B相當于消費者:
這個模型所需要的對象有:
- 生產者
- 消費者
- 容器
為什么要用這個模型
-
在多線程開發中,如果生產者生產數據的速度很快,而消費者消費數據的速度很慢,那么生產者就必須等待消費者消費完數據才能夠繼續生產數據,因為生產過多的數據可能會導致存儲不足;同理如果消費者的速度大于生產者那么消費者就會經常處理等待狀態,所以為了達到生產者和消費者生產數據和消費數據之間的平衡,那么就需要一個緩沖區用來存儲生產者生產的數據,所以就引入了這個模式。
-
簡單來說,這里緩沖區的作用就是為了平衡生產者和消費者的數據處理能力,一方面起到緩存作用,另一方面達到解耦合作用。那么如何去解決這個問題,需要使用條件變量和信號量來解決這個問題
生產者-消費者模型特點
-
保證生產者不會在緩沖區滿的時候繼續向緩沖區中放入數據,而消費者也不會在緩沖區空的時候消耗數據。
-
當緩沖區滿時,生產者會進入休眠狀態,當下次消費者開始消耗緩沖區的數據時,生產者才會被喚醒,開始往緩沖區中添加數據;當緩沖區空時,消費者也會進入休眠狀態,直到生產者往緩沖區中添加數據時才會被喚醒。
五、條件變量
- 條件變量不是鎖,但是它可以引起線程阻塞,滿足條件后解除阻塞,它是配合互斥量來實現的,當然它也有一個結構體
pthread_cond_t
,其操作的代碼有如下:
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);int pthread_cond_destroy(pthread_cond_t *cond);int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);int pthread_cond_signal(pthread_cond_t *cond);int pthread_cond_broadcast(pthread_cond_t *cond);
5.1 功能描述
通過條件變量(pthread_cond_t
)實現生產者-消費者同步:生產者生成數據后通知消費者,消費者無數據時阻塞等待。
typedef struct Node
{int value;struct Node *next;
} Node;pthread_cond_t cond;
struct Node *head = NULL;
void *produceTask(void *arg)
{for (int i = 0; i < 100; ++i){pthread_mutex_lock(&mutex);struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));newNode->value = i;newNode->next = head;head = newNode;std::cout << "----------Produce Task----------" << std::endl;std::cout << "value = " << newNode->value << " thread ID = " << pthread_self() << std::endl;pthread_mutex_unlock(&mutex);pthread_cond_signal(&cond); // 通知消費線程usleep(1000);}return NULL;
}void *consumeTask(void *arg)
{for (int i = 0; i < 50; ++i){pthread_mutex_lock(&mutex);Node *node = head;if (node != NULL){std::cout << "----------Consume Task----------" << std::endl;std::cout << "value = " << node->value << " thread ID = " << pthread_self() << std::endl;head = head->next;free(node);node = NULL;}else{pthread_cond_wait(&cond, &mutex); // 阻塞等待,讓出鎖,直到收到信號,重新加鎖}pthread_mutex_unlock(&mutex);}return NULL;
}void test3()
{pthread_cond_init(&cond, NULL);pthread_mutex_init(&mutex, NULL);pthread_t t1, t2, t3;pthread_create(&t1, NULL, produceTask, NULL);pthread_create(&t2, NULL, consumeTask, NULL);pthread_create(&t3, NULL, consumeTask, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_cond_destroy(&cond);pthread_mutex_destroy(&mutex);
}
5.2 核心代碼分析
pthread_cond_t cond;
struct Node *head = NULL;void *produceTask(void *arg) {for (int i = 0; i < 100; ++i) {pthread_mutex_lock(&mutex);// 生產數據(簡化為鏈表頭插入)Node *newNode = malloc(sizeof(Node));newNode->value = i;newNode->next = head;head = newNode;pthread_mutex_unlock(&mutex);pthread_cond_signal(&cond); // 通知消費者有新數據usleep(1000);}return NULL;
}void *consumeTask(void *arg) {for (int i = 0; i < 50; ++i) {pthread_mutex_lock(&mutex);// 無數據時等待條件變量while (head == NULL) {pthread_cond_wait(&cond, &mutex); // 釋放鎖并阻塞,喚醒時重新加鎖}// 消費數據Node *node = head;head = head->next;free(node);pthread_mutex_unlock(&mutex);}return NULL;
}
5.3 關鍵API詳解:條件變量(pthread_cond_t
)
5.3.1 初始化與銷毀
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr
);int pthread_cond_destroy(pthread_cond_t *cond);
- 參數:
attr
:條件變量屬性,NULL表示默認屬性。- 常見屬性:
PTHREAD_PROCESS_SHARED
:進程間共享(需配合共享內存使用)。
- 常見屬性:
5.3.2 等待函數
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex
);int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime
);
- 參數:
mutex
:關聯的互斥鎖,用于保護條件判斷。abstime
:超時時間(絕對時間)。
- 執行流程:
- 原子性地釋放
mutex
并阻塞當前線程。 - 被喚醒時,重新獲取
mutex
并返回。
- 原子性地釋放
- 返回值:
pthread_cond_wait
:成功返回0。pthread_cond_timedwait
:超時返回ETIMEDOUT
。
5.3.3 通知函數
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
- 區別:
pthread_cond_signal
:喚醒至少一個等待線程(由調度器決定)。pthread_cond_broadcast
:喚醒所有等待線程(適用于多個消費者場景)。
- 注意:
- 通常在解鎖后調用,但在鎖內調用更安全(避免喚醒丟失)。
5.4.4 運行結果
六、信號量
- 信號量這個東西也是用于阻塞線程的,相當于是在亮燈,他只能告訴線程當前數據是否可讀可寫,但是它并不能真正的保證數據的安全問題,所以也需要配合互斥鎖的使用,當然也有一個結構體
sem_t
,其操作的代碼有如下:
int sem_init(sem_t *sem, int pshared, unsigned int value);int sem_init(sem_t *sem, int pshared, unsigned int value);int sem_wait(sem_t *sem);int sem_trywait(sem_t *sem);int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);int sem_post(sem_t *sem);int sem_getvalue(sem_t *sem, int *sval);
6.1 功能描述
使用計數信號量(sem_t
)控制生產者和消費者的并發數量:
pSem
:控制生產者可使用的空閑槽位數量(初始值5)。cSem
:控制消費者可使用的數據項數量(初始值0)。
sem_t pSem, cSem;void *produceTaskSem(void *arg)
{for (int i = 0; i < 100; ++i){sem_wait(&pSem); // producer信號量-1pthread_mutex_lock(&mutex);struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));newNode->value = i;newNode->next = head;head = newNode;int semCount = 0;int ret = sem_getvalue(&pSem,&semCount);std::cout << "----------Produce Task----------" << std::endl;std::cout << "value = " << newNode->value << " thread ID = " << pthread_self() << "semCount = " << semCount <<std::endl;pthread_mutex_unlock(&mutex);sem_post(&cSem); // consumer信號量+1}return NULL;
}void *consumeTaskSem(void *arg)
{for (int i = 0; i < 100; ++i){sem_wait(&cSem); // consumer 信號量-1pthread_mutex_lock(&mutex);Node *node = head;int semCount = 0;int ret = sem_getvalue(&cSem,&semCount);std::cout << "----------Consume Task----------" << std::endl;std::cout << "value = " << node->value << " thread ID = " << pthread_self() << "semCount = " << semCount << std::endl;head = head->next;free(node);node = NULL;pthread_mutex_unlock(&mutex);sem_post(&pSem); // producer 信號量+1}return NULL;
}
void test4()
{pthread_mutex_init(&mutex, NULL);sem_init(&cSem, 0, 0);sem_init(&pSem, 0, 5);pthread_t pThread[5], cThread[5];for (int i = 0; i < 5; ++i){pthread_create(&pThread[i], NULL, produceTaskSem, NULL);pthread_create(&cThread[i], NULL, consumeTaskSem, NULL);}for (int i = 0; i < 5; ++i){pthread_join(pThread[i], NULL);pthread_join(cThread[i], NULL);}pthread_mutex_destroy(&mutex);sem_destroy(&pSem);sem_destroy(&cSem);
}
6.2 核心代碼分析
sem_t pSem, cSem;void *produceTaskSem(void *arg)
{for (int i = 0; i < 100; ++i){sem_wait(&pSem); // producer信號量-1pthread_mutex_lock(&mutex);struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));newNode->value = i;newNode->next = head;head = newNode;int semCount = 0;int ret = sem_getvalue(&pSem,&semCount);std::cout << "----------Produce Task----------" << std::endl;std::cout << "value = " << newNode->value << " thread ID = " << pthread_self() << "semCount = " << semCount <<std::endl;pthread_mutex_unlock(&mutex);sem_post(&cSem); // consumer信號量+1}return NULL;
}void *consumeTaskSem(void *arg)
{for (int i = 0; i < 100; ++i){sem_wait(&cSem); // consumer 信號量-1pthread_mutex_lock(&mutex);Node *node = head;int semCount = 0;int ret = sem_getvalue(&cSem,&semCount);std::cout << "----------Consume Task----------" << std::endl;std::cout << "value = " << node->value << " thread ID = " << pthread_self() << "semCount = " << semCount << std::endl;head = head->next;free(node);node = NULL;pthread_mutex_unlock(&mutex);sem_post(&pSem); // producer 信號量+1}return NULL;
}
6.3 關鍵API詳解:信號量(sem_t
)
6.3.1 初始化與銷毀
int sem_init(sem_t *sem,int pshared,unsigned int value
);int sem_destroy(sem_t *sem);
- 參數:
pshared
:- 0:信號量在線程間共享(保存在進程內存中)。
- 非0:信號量在進程間共享(需配合共享內存使用)。
value
:信號量初始值(如5表示5個空閑槽位)。
- 注意:
- 銷毀前需確保沒有線程在等待該信號量。
6.3.2 P操作(等待)
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *restrict sem,const struct timespec *restrict abstime
);
- 執行邏輯:
sem_wait
:信號量減1,若結果為負則阻塞。sem_trywait
:非阻塞版本,信號量不足時返回EAGAIN
。sem_timedwait
:超時返回ETIMEDOUT
。
6.3.3 V操作(釋放)
int sem_post(sem_t *sem);
- 執行邏輯:
- 信號量加1。
- 若有線程因等待該信號量而阻塞,則喚醒其中一個。
6.3.4 獲取當前值(調試用)
int sem_getvalue(sem_t *restrict sem,int *restrict sval
);
- 返回值:
- 成功:返回0,
*sval
存儲當前信號量值。 - 失敗:返回錯誤碼。
- 成功:返回0,
- 注意:
- 返回值可能在調用后立即被其他線程修改,僅供調試參考。
6.3.5 運行結果
七、多線程同步機制對比
機制 | 核心功能 | 關鍵API | 適用場景 |
---|---|---|---|
互斥鎖 | 保證互斥訪問 | lock , unlock | 所有互斥場景 |
讀寫鎖 | 多讀單寫優化 | rdlock , wrlock , unlock | 讀多寫少場景 |
條件變量 | 線程間條件同步 | wait , signal , broadcast | 生產者-消費者、任務通知等 |
信號量 | 控制有限資源的并發訪問 | wait , post | 連接池、線程池等資源管理 |
更多資料:https://github.com/0voice