【Linux】pthread多線程同步

參考文章:https://blog.csdn.net/Alkaid2000/article/details/128121066

一、線程同步

  • 線程的主要優勢在于,能夠通過全局變量來共享信息。不過,這種便攜的共享是有代價的;必須確保多個線程不會同時修改同一變量,或者某一線程不會讀取正在由其他線程修改的變量。(否則會出現數據安全現象)

  • 臨界區是指訪問某一共享資源的代碼片段,并且這段代碼的執行應為原子操作,也就是同時訪問同一共享資源的其他線程不應中斷該片段的執行。

  • 線程同步:即當有一個線程在對內存進行操作時,其他線程都不可以對這個內存地址進行操作,直到線程完成操作,其他線程才能對內存地址進行操作,而其他線程則處于等待狀態,肯定會影響效率,但是起碼他只是影響了代碼的一小部分。

二、互斥鎖

  • 為避免線程更新共享變量時出現問題,可以使用互斥量(mutex——mutual exclusion)來確保同時僅有一個線程可以訪問某項共享資源。可以使用互斥量來保證對任意共享資源的原子訪問。

  • 互斥量有兩種狀態:已鎖定(locked)和未鎖定(unlocked)。任何時候,至多只有一個線程可以鎖定該互斥量。試圖對已經鎖定的某一互斥量再次加鎖,將可能阻塞線程或報錯失敗,具體取決于加鎖時使用的方法。

  • 一旦線程鎖定互斥量,隨即稱為該互斥量的所有者,只有所有者才能給互斥量解鎖。一般情況下,對每一共享資源(可能由多個相關變量組成)會使用不同的互斥量,每一線程在訪問同一資源時將采用如下協議:

    1. 針對共享資源鎖定互斥量。
    2. 訪問共享資源。
    3. 對互斥量解鎖。
  • 如果多個線程試圖執行這一塊代碼(一個臨界區),事實上只有一個線程能夠持有該互斥量(其他線程將遭到阻塞),即同時只有一個線程能夠進入這段代碼區域。

在這里插入圖片描述

  • 關于互斥量有如下的操作函數,與線程屬性相似,它具有一個互斥量類型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;
}
如何避免死鎖

當兩個線程為了保護兩個不同的共享資源而使用了兩個互斥鎖,那么這兩個互斥鎖應用不當的時候,可能會造成兩個線程都在等待對方釋放鎖,在沒有外力的作用下,這些線程會一直相互等待,就沒辦法繼續運行,這種情況就是發生了死鎖,那么死鎖只有同時滿足以下四個條件才會發生:

  1. 互斥條件。
  2. 持有并等待條件。
  3. 不可剝奪條件。
  4. 環路等待條件。

互斥條件:對于互斥條件是指多個線程不能同時同一個資源:如果線程 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:超時時間(絕對時間)。
  • 執行流程
    1. 原子性地釋放mutex并阻塞當前線程。
    2. 被喚醒時,重新獲取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存儲當前信號量值。
    • 失敗:返回錯誤碼。
  • 注意
    • 返回值可能在調用后立即被其他線程修改,僅供調試參考。
6.3.5 運行結果

在這里插入圖片描述

七、多線程同步機制對比

機制核心功能關鍵API適用場景
互斥鎖保證互斥訪問lock, unlock所有互斥場景
讀寫鎖多讀單寫優化rdlock, wrlock, unlock讀多寫少場景
條件變量線程間條件同步wait, signal, broadcast生產者-消費者、任務通知等
信號量控制有限資源的并發訪問wait, post連接池、線程池等資源管理

更多資料:https://github.com/0voice

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/83734.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/83734.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/83734.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spring框架學習day7--SpringWeb學習(概念與搭建配置)

SpringWeb1.SpringWeb特點2.SpringWeb運行流程3.SpringWeb組件4.搭建項目結構圖&#xff1a;4.1導入jar包4.2在Web.xml配置**4.2.1配置統一攔截分發器 DispatcherServlet**4.2.2開啟SpringWeb注解&#xff08;spring.xml&#xff09; 5.處理類的搭建6.SpringWeb請求流程(自己理…

業務到解決方案構想

解決方案構想的核心理解 解決方案構想是連接業務需求與技術實現的關鍵橋梁&#xff0c;從您描述的內容和我的理解&#xff0c;這個階段的核心點包括&#xff1a; 核心要點解讀 轉化視角&#xff1a;將業務視角的需求轉變為解決方案視角 業務能力探索階段識別了"做什么&q…

jvm學習第1day jvm簡介,棧溢出、堆溢出

jvm學習第1day jvm簡介&#xff0c;棧溢出、堆溢出 jvm簡介棧線程安全棧溢出線程運行診斷堆堆溢出 方法區方法區內存溢出常量池和運行時常量池 jvm簡介 jvm 是編譯后的字節碼文件運行的環境&#xff0c; 因此各個平臺有了jvm可以運行java.class文件&#xff0c;這是Java跨平臺…

關于神經網絡中的激活函數

這篇博客主要介紹一下神經網絡中的激活函數以及為什么要存在激活函數。 首先&#xff0c;我先做一個簡單的類比&#xff1a;激活函數的作用就像給神經網絡里的 “數字信號” 加了一個 “智能閥門”&#xff0c;讓機器能學會像人類一樣思考復雜問題。 沒有激活i函數的神經網絡…

免費無限使用GPT Plus、Claude Pro、Grok Super、Deepseek滿血版

滲透智能-ShirtAI&#xff0c;可以免費無限使用GPT Plus、Claude Pro、Grok Super、Deepseek滿血版、除此之外還能免費使用AI搜索、Gemini AI、AI照片修復、AI橡皮擦、AI去背景、AI智能摳圖、AI證件照、OCR識別、在線思維導圖、在線繪圖工具、PDF工具箱、PDF翻譯。 傳送入口&a…

阿里云 Linux 搭建郵件系統全流程及常見問題解決

阿里云 Linux 搭建 [conkl.com]郵件系統全流程及常見問題解決 目錄 阿里云 Linux 搭建 [conkl.com]郵件系統全流程及常見問題解決一、前期準備&#xff08;關鍵配置需重點檢查&#xff09;1.1 服務器與域名準備1.2 系統初始化&#xff08;必做操作&#xff09; 二、核心組件安裝…

python版若依框架開發:項目結構解析

python版若依框架開發 從0起步&#xff0c;揚帆起航。 python版若依部署代碼生成指南&#xff0c;迅速落地CURD&#xff01;項目結構解析 文章目錄 python版若依框架開發前端后端 前端 后端

RabbitMQ 的異步化、解耦和流量削峰三大核心機制

RabbitMQ 的異步化、解耦和流量削峰三大核心機制 RabbitMQ 是解決數據庫高并發問題的利器&#xff0c;通過異步化、解耦和流量削峰三大核心機制保護數據庫。下面從設計思想到具體實現&#xff0c;深入剖析 RabbitMQ 應對高并發的完整方案&#xff1a; 一、數據庫高并發核心痛點…

前端沒有“秦始皇“,但可以做跨端的王[特殊字符]

前端各領域的 “百家爭鳴” 框架之爭&#xff1a;有 React、Vue、Angular 等多種框架。它們各有優缺點&#xff0c;開發者之間還存在鄙視鏈&#xff0c;比如 Vue 嫌 React 難用&#xff0c;React 嫌 Vue 不夠靈活。樣式處理&#xff1a; CSS 預處理器&#xff1a;像 Sass、Les…

Spring Boot-面試題(52)

摘要&#xff1a; 1、通俗易懂&#xff0c;適合小白 2、僅做面試復習用&#xff0c;部分來源網絡&#xff0c;博文免費&#xff0c;知識無價&#xff0c;侵權請聯系&#xff01; 1. 什么是 Spring Boot 框架&#xff1f; Spring Boot 是基于 Spring 框架的快速開發框架&#…

JVM——JVM中的字節碼:解碼Java跨平臺的核心引擎

引入 在Java的技術版圖中&#xff0c;字節碼&#xff08;Bytecode&#xff09;是連接源代碼與機器世界的黃金橋梁。當開發者寫下第一行public class HelloWorld時&#xff0c;編譯器便開始了一場精密的翻譯工程——將人類可讀的Java代碼轉化為JVM能夠理解的字節碼指令。這些由…

Java中的JSONObject詳解:從基礎到高級應用

Java中的JSONObject詳解&#xff1a;從基礎到高級應用 在當今前后端分離的架構中&#xff0c;JSONObject已成為Java開發者處理JSON數據的瑞士軍刀。本文將深入解析JSONObject的核心機制與實戰技巧。 一、JSONObject的本質與實現庫 1.1 核心定位 JSONObject是Java中表示JSON對…

在 SpringBoot+Tomcat 環境中 線程安全問題的根本原因以及哪些變量會存在線程安全的問題。

文章目錄 前言Tomcat SpringBoot單例加載結果分析多例加載&#xff1a;結果分析&#xff1a; 哪些變量存在線程安全的問題&#xff1f;線程不安全線程安全 總結 前言 本文帶你去深入理解為什么在web環境中(Tomcat SpringBoot)會存在多線程的問題以及哪些變量會存在線程安全的…

npm install 相關命令

npm install 相關命令 基本安裝命令 # 安裝 package.json 中列出的所有依賴 npm install npm i # 簡寫形式# 安裝特定包 npm install <package-name># 安裝特定版本 npm install <package-name><version>依賴類型選項 # 安裝為生產依賴&#xff08;默認&…

貪心算法應用:最小反饋頂點集問題詳解

貪心算法應用&#xff1a;最小反饋頂點集問題詳解 1. 問題定義與背景 1.1 反饋頂點集定義 反饋頂點集(Feedback Vertex Set, FVS)是指在一個有向圖中&#xff0c;刪除該集合中的所有頂點后&#xff0c;圖中將不再存在任何有向環。換句話說&#xff0c;反饋頂點集是破壞圖中所…

BiliNote部署實踐

? 開源地址&#xff1a; https://github.com/JefferyHcool/BiliNote &#x1f680; 快速開始 1. 克隆倉庫 git clone https://github.com/JefferyHcool/BiliNote.git cd BiliNote mv .env.example .env2. 啟動后端&#xff08;FastAPI&#xff09; cd backend pip insta…

用go從零構建寫一個RPC(4)--gonet網絡框架重構+聚集發包

在追求高性能的分布式系統中&#xff0c;RPC 框架的底層網絡能力和數據傳輸效率起著決定性作用。經過幾輪迭代優化&#xff0c;我完成了第四版本的 RPC 框架。相比以往版本&#xff0c;這一版本的最大亮點在于 重寫了底層網絡框架 和 實現了發送端的數據聚集機制&#xff0c;這…

MySQL 高可用基石 - 復制監控與常見 HA 方案

MySQL 高可用基石 - 復制監控與常見 HA 方案 MySQL 復制核心原理 MySQL 復制允許數據從一個 MySQL 數據庫服務器(稱為主庫 - Primary,舊稱 Master)復制到一個或多個其他的 MySQL 服務器(稱為從庫 - Replica,舊稱 Slave)。 復制的主要目的: 高可用性 (High Availability…

微信小程序(uniapp)自定義 TabBar

微信小程序&#xff08;uniapp&#xff09;自定義 TabBar 實現指南 在微信小程序開發中&#xff0c;TabBar 是底部導航欄的重要組件&#xff0c;但官方提供的 TabBar 樣式和功能較為基礎&#xff0c;無法滿足所有項目的需求。本文將詳細介紹如何在 uniapp 中實現自定義 TabBar…

MLP實戰二:MLP 實現圖像數字多分類

任務 實戰&#xff08;二&#xff09;&#xff1a;MLP 實現圖像多分類 基于 mnist 數據集&#xff0c;建立 mlp 模型&#xff0c;實現 0-9 數字的十分類 task: 1、實現 mnist 數據載入&#xff0c;可視化圖形數字&#xff1b; 2、完成數據預處理&#xff1a;圖像數據維度轉換與…