網(wǎng)站建設(shè)建網(wǎng)站電商網(wǎng)站商品頁的優(yōu)化目標(biāo)是什么
多線程和線程同步復(fù)習(xí)
- 進程線程區(qū)別
- 創(chuàng)建線程
- 線程退出
- 線程回收
- 全局寫法
- 傳參寫法
- 線程分離
- 線程同步
- 同步方式
- 互斥鎖
- 互斥鎖進行線程同步
- 死鎖
- 讀寫鎖
- api細(xì)說
- 讀寫鎖進行線程同步
- 條件變量
- 生產(chǎn)者消費者案例
- 問題解答
- 加強版生產(chǎn)者消費者
- 總結(jié)
- 信號量
- 信號量實現(xiàn)生產(chǎn)者消費者同步-->一個資源
- 信號量實現(xiàn)生產(chǎn)者消費者同步-->多個資源
進程線程區(qū)別
- 進程是資源分配的最小單位,線程是操作系統(tǒng)調(diào)度執(zhí)行的最小單位。
- 進程有自己獨立的地址空間, 多個線程共用同一個地址空間
- 線程更加節(jié)省系統(tǒng)資源, 效率不僅可以保持的, 而且能夠更高
在一個地址空間中多個線程獨享: 每個線程都有屬于自己的棧區(qū), 寄存器(內(nèi)核中管理的)
在一個地址空間中多個線程共享: 代碼段, 堆區(qū), 全局?jǐn)?shù)據(jù)區(qū), 打開的文件(文件描述符表)都是線程共享的- 線程是程序的最小執(zhí)行單位, 進程是操作系統(tǒng)中最小的資源分配單位
每個進程對應(yīng)一個虛擬地址空間,一個進程只能搶一個CPU時間片
一個地址空間中可以劃分出多個線程, 在有效的資源基礎(chǔ)上, 能夠搶更多的CPU時間片- CPU的調(diào)度和切換: 線程的上下文切換比進程要快的多
- 線程更加廉價, 啟動速度更快, 退出也快, 對系統(tǒng)資源的沖擊小。
創(chuàng)建線程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>// 子線程的處理代碼
void* working(void* arg)
{printf("我是子線程, 線程ID: %ld\n", pthread_self());for(int i=0; i<9; ++i){printf("child == i: = %d\n", i);}return NULL;
}int main()
{// 1. 創(chuàng)建一個子線程pthread_t tid;pthread_create(&tid, NULL, working, NULL);printf("子線程創(chuàng)建成功, 線程ID: %ld\n", tid);// 2. 子線程不會執(zhí)行下邊的代碼, 主線程執(zhí)行printf("我是主線程, 線程ID: %ld\n", pthread_self());for(int i=0; i<3; ++i){printf("i = %d\n", i);}// 休息, 休息一會兒...sleep(1);// >>>>>>>>>>>>>>>>>>>> 讓主線程休息一會給子線程運行return 0;
}
子線程被創(chuàng)建出來之后需要搶cpu時間片, 搶不到就不能運行,如果主線程退出了, 虛擬地址空間就被釋放了, 子線程就一并被銷毀了。
這里的解決方案,讓子線程執(zhí)行完畢, 主線程再退出, 可以在主線程中添加掛起函數(shù) sleep();
線程退出
子線程退出這個地址空間是存在的不影響主線程,這個線程退出函數(shù)實際上是服務(wù)于主線程的。這個函數(shù)作用是讓主線程退出后子線程繼續(xù)執(zhí)行,而不回收這塊虛擬地址空間,當(dāng)子線程退出后這塊虛擬地址空間就會被操作系統(tǒng)回收。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>// 子線程的處理代碼
void* working(void* arg)
{printf("我是子線程, 線程ID: %ld\n", pthread_self());for(int i=0; i<9; ++i){printf("child == i: = %d\n", i);}return NULL;
}int main()
{// 1. 創(chuàng)建一個子線程pthread_t tid;pthread_create(&tid, NULL, working, NULL);printf("子線程創(chuàng)建成功, 線程ID: %ld\n", tid);pthread_exit(NULL);return 0;
}
可以看出來主線程退出后是不影響子線程的執(zhí)行
線程回收
#include <pthread.h>
// 這是一個阻塞函數(shù), 子線程在運行這個函數(shù)就阻塞
// 子線程退出, 函數(shù)解除阻塞, 回收對應(yīng)的子線程資源, 類似于回收進程使用的函數(shù) wait()
int pthread_join(pthread_t thread, void **retval);
全局寫法
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>struct Test
{int num;int age;
};
struct Test t;
// 子線程的處理代碼
void* working(void* arg)
{for(int i=0; i<5; ++i){printf("子線程:i = %d\n", i);}printf("子線程ID: %ld\n", pthread_self());// struct Test t; >>>>>>>>>>> errt.num=100;t.age=66;pthread_exit(&t);return NULL;
}
int main()
{pthread_t tid;pthread_create(&tid, NULL, working, NULL);printf("主線程:%ld\n",pthread_self());void* ptr;pthread_join(tid,&ptr);struct Test* pt=(struct Test*)ptr;printf("num: %d,age = %d\n",pt->num,pt->age);return 0;
}
pthread_join(tid, &ptr); 的作用是等待子線程 tid 執(zhí)行完畢,并將子線程通過 pthread_exit 返回的指針值保存到 ptr 中。
傳參寫法
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>struct Test
{int num;int age;
};// 子線程的處理代碼
void* working(void* arg)
{for(int i=0; i<5; ++i){printf("子線程:i = %d\n", i);}printf("子線程ID: %ld\n", pthread_self());struct Test *t=(struct Test*)arg;t->num=100;t->age=66;pthread_exit(&t);return NULL;
}int main()
{pthread_t tid;struct Test t;pthread_create(&tid, NULL, working, &t);// >>>> 把主線程的??臻g給了子線程printf("主線程:%ld\n",pthread_self());void* ptr;pthread_join(tid,&ptr);printf("num: %d,age = %d\n",t.num,t.age);// >>>>>直接打印即可return 0;
}
pthread_join 只是等待線程 tid 執(zhí)行完成,并獲取線程 tid 的返回值。此時并沒有釋放或銷毀傳遞給線程的參數(shù) t
線程分離
在某些情況下,程序中的主線程有屬于自己的業(yè)務(wù)處理流程,如果讓主線程負(fù)責(zé)子線程的資源回收,調(diào)用pthread_join()只要子線程不退出主線程就會一直被阻塞,主要線程的任務(wù)也就不能被執(zhí)行了。
調(diào)用這個函數(shù)之后指定的子線程就可以和主線程分離,當(dāng)子線程退出的時候,其占用的內(nèi)核資源就被系統(tǒng)的其他進程接管并回收了
當(dāng)主線程退出的時候如果子線程還在工作直接就沒了,因為虛擬地址空間被釋放了
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>// 子線程的處理代碼
void* working(void* arg)
{printf("我是子線程, 線程ID: %ld\n", pthread_self());for(int i=0; i<9; ++i){printf("child == i: = %d\n", i);}return NULL;
}int main()
{// 1. 創(chuàng)建一個子線程pthread_t tid;pthread_create(&tid, NULL, working, NULL);printf("子線程創(chuàng)建成功, 線程ID: %ld\n", tid);// 2. 子線程不會執(zhí)行下邊的代碼, 主線程執(zhí)行printf("我是主線程, 線程ID: %ld\n", pthread_self());for(int i=0; i<3; ++i){printf("i = %d\n", i);}// 設(shè)置子線程和主線程分離pthread_detach(tid);// 讓主線程自己退出即可pthread_exit(NULL);return 0;
}
子線程實際上被操作系統(tǒng)回收了
線程同步
線程同步并不是多個線程同時對內(nèi)存進行訪問,而是按照先后順序依次進行的。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>#define MAX 50
// 全局變量
int number;// 線程處理函數(shù)
void* funcA_num(void* arg)
{for(int i=0; i<MAX; ++i){int cur = number;cur++;usleep(10);// >>>>>>>>>> 微秒number = cur;printf("Thread A, id = %lu, number = %d\n", pthread_self(), number);}return NULL;
}void* funcB_num(void* arg)
{for(int i=0; i<MAX; ++i){int cur = number;cur++;number = cur;printf("Thread B, id = %lu, number = %d\n", pthread_self(), number);usleep(5);}return NULL;
}int main(int argc, const char* argv[])
{pthread_t p1, p2;// 創(chuàng)建兩個子線程pthread_create(&p1, NULL, funcA_num, NULL);pthread_create(&p2, NULL, funcB_num, NULL);// 阻塞,資源回收pthread_join(p1, NULL);pthread_join(p2, NULL);return 0;
}
這里創(chuàng)建了兩個線程 funcA_num 和 funcB_num,它們分別對共享全局變量 number 進行加一操作并輸出結(jié)果。然而,由于缺少對 number 的同步處理,會導(dǎo)致線程競爭,從而出現(xiàn)不一致的結(jié)果。這是因為在多線程環(huán)境中,number 的讀寫操作并不是原子性的,所以多個線程可能會對 number 進行相互覆蓋的操作,導(dǎo)致不正確的輸出結(jié)果。
具體解釋:如果線程A執(zhí)行這個過程期間就失去了CPU時間片,線程A被掛起了最新的數(shù)據(jù)沒能更新到物理內(nèi)存。線程B變成運行態(tài)之后從物理內(nèi)存讀數(shù)據(jù),很顯然它沒有拿到最新數(shù)據(jù),只能基于舊的數(shù)據(jù)往后數(shù),然后失去CPU時間片掛起。線程A得到CPU時間片變成運行態(tài),第一件事兒就是將上次沒更新到內(nèi)存的數(shù)據(jù)更新到內(nèi)存,但是這樣會導(dǎo)致線程B已經(jīng)更新到內(nèi)存的數(shù)據(jù)被覆蓋,活兒白干了,最終導(dǎo)致有些數(shù)據(jù)會被重復(fù)數(shù)很多次。
同步方式
對于多個線程訪問共享資源出現(xiàn)數(shù)據(jù)混亂的問題,需要進行線程同步。常用的線程同步方式有四種:互斥鎖、讀寫鎖、條件變量、信號量。所謂的共享資源就是多個線程共同訪問的變量,這些變量通常為全局?jǐn)?shù)據(jù)區(qū)變量或者堆區(qū)變量,這些變量對應(yīng)的共享資源也被稱之為臨界資源。
通過鎖機制能保證臨界區(qū)代碼最多只能同時有一個線程訪問,這樣并行訪問就變?yōu)榇性L問了。
互斥鎖
一般情況下,每一個共享資源對應(yīng)一個把互斥鎖,鎖的個數(shù)和線程的個數(shù)無關(guān)。
// 初始化互斥鎖
// *******restrict: 是一個關(guān)鍵字, 用來修飾指針, 只有這個關(guān)鍵字修飾的指針可以訪問指向的內(nèi)存地址, 其他指針是不行的********
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_trylock(pthread_mutex_t *mutex);
互斥鎖進行線程同步
加鎖和解鎖之間放的是臨界區(qū),加鎖的是臨界區(qū)的資源
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>#define MAX 50
// 全局變量
int number=0;
pthread_mutex_t mutex;// 線程處理函數(shù)
void* funcA_num(void* arg)
{for(int i=0; i<MAX; ++i){pthread_mutex_lock(&mutex);int cur = number;cur++;usleep(10);number = cur;pthread_mutex_unlock(&mutex);printf("Thread A, id = %lu, number = %d\n", pthread_self(), number);}return NULL;
}void* funcB_num(void* arg)
{for(int i=0; i<MAX; ++i){pthread_mutex_lock(&mutex);int cur = number;cur++;number = cur;pthread_mutex_unlock(&mutex);printf("Thread B, id = %lu, number = %d\n", pthread_self(), number);usleep(5);}return NULL;
}int main(int argc, const char* argv[])
{pthread_t p1, p2;pthread_mutex_init(&mutex,NULL);// 創(chuàng)建兩個子線程pthread_create(&p1, NULL, funcA_num, NULL);pthread_create(&p2, NULL, funcB_num, NULL);// 阻塞,資源回收pthread_join(p1, NULL);pthread_join(p2, NULL);pthread_mutex_destroy(&mutex);return 0;
}
死鎖
加鎖之后忘記解鎖
重復(fù)加鎖, 造成死鎖
在程序中有多個共享資源, 因此有很多把鎖,隨意加鎖,導(dǎo)致相互被阻塞
讀寫鎖
之所以稱其為讀寫鎖,是因為這把鎖既可以鎖定讀操作,也可以鎖定寫操作
使用讀寫鎖鎖定了讀操作,需要先解鎖才能去鎖定寫操作,反之亦然
特性:
使用讀寫鎖的讀鎖鎖定了臨界區(qū),線程對臨界區(qū)的訪問是并行的,讀鎖是共享的。
使用讀寫鎖的寫鎖鎖定了臨界區(qū),線程對臨界區(qū)的訪問是串行的,寫鎖是獨占的。
使用讀寫鎖分別對兩個臨界區(qū)加了讀鎖和寫鎖,兩個線程要同時訪問者兩個臨界區(qū),訪問寫鎖臨界區(qū)的線程繼續(xù)運行,訪問讀鎖臨界區(qū)的線程阻塞,因為寫鎖比讀鎖的優(yōu)先級高。 >>>>>>>>>>>>>>>> 場景:讀進程大于寫進程數(shù)的時候
api細(xì)說
// 在程序中對讀寫鎖加讀鎖, 鎖定的是讀操作
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//調(diào)用這個函數(shù),如果讀寫鎖是打開的,那么加鎖成功;**如果讀寫鎖已經(jīng)鎖定了讀操作**,**調(diào)用這個函數(shù)依然可以加鎖成功,因為讀鎖是共享的**;如果讀寫鎖已經(jīng)鎖定了寫操作,調(diào)用這個函數(shù)的線程會被阻塞。// 在程序中對讀寫鎖加寫鎖, 鎖定的是寫操作
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
//調(diào)用這個函數(shù),如果讀寫鎖是打開的,那么加鎖成功;**如果讀寫鎖已經(jīng)鎖定了讀操作或者鎖定了寫操作,調(diào)用這個函數(shù)的線程會被阻塞。**
讀寫鎖進行線程同步
8個線程操作同一個全局變量,3個線程不定時寫同一全局資源,5個線程不定時讀同一全局資源。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>// 全局變量
int number = 0;// 定義讀寫鎖
pthread_rwlock_t rwlock;// 寫的線程的處理函數(shù)
void* writeNum(void* arg)
{while(1){pthread_rwlock_wrlock(&rwlock);int cur = number;cur ++;number = cur;printf("++寫操作完畢, number : %d, tid = %ld\n", number, pthread_self());pthread_rwlock_unlock(&rwlock);// 添加sleep目的是要看到多個線程交替工作sleep(rand() % 10);}return NULL;
}// 讀線程的處理函數(shù)
// 多個線程可以如果處理動作相同, 可以使用相同的處理函數(shù)
// 每個線程中的棧資源是獨享
void* readNum(void* arg)
{while(1){pthread_rwlock_rdlock(&rwlock);printf("--全局變量number = %d, tid = %ld\n", number, pthread_self());pthread_rwlock_unlock(&rwlock);sleep(rand() % 10);}return NULL;
}int main()
{// 初始化讀寫鎖pthread_rwlock_init(&rwlock, NULL);// 3個寫線程, 5個讀的線程pthread_t wtid[3];pthread_t rtid[5];for(int i=0; i<3; ++i){pthread_create(&wtid[i], NULL, writeNum, NULL);}for(int i=0; i<5; ++i){pthread_create(&rtid[i], NULL, readNum, NULL);}// 釋放資源for(int i=0; i<3; ++i){pthread_join(wtid[i], NULL);}for(int i=0; i<5; ++i){pthread_join(rtid[i], NULL);}// 銷毀讀寫鎖pthread_rwlock_destroy(&rwlock);return 0;
}
條件變量
嚴(yán)格意義上來說,條件變量的主要作用不是處理線程同步, 而是進行線程的阻塞。如果在多線程程序中只使用條件變量無法實現(xiàn)線程的同步, 必須要配合互斥鎖來使用。雖然條件變量和互斥鎖都能阻塞線程,但是二者的效果是不一樣的,二者的區(qū)別如下:
條件變量只有在滿足指定條件下才會阻塞線程,如果條件不滿足,多個線程可以同時進入臨界區(qū),同時讀寫臨界資源,這種情況下還是會出現(xiàn)共享資源中數(shù)據(jù)的混亂。
生產(chǎn)者消費者案例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>// 鏈表的節(jié)點
struct Node
{int number;struct Node* next;
};// 定義條件變量, 控制消費者線程
pthread_cond_t cond;
// 互斥鎖變量
pthread_mutex_t mutex;
// 指向頭結(jié)點的指針
struct Node * head = NULL;// 生產(chǎn)者的回調(diào)函數(shù)
void* producer(void* arg)
{// 一直生產(chǎn)while(1){pthread_mutex_lock(&mutex);// 創(chuàng)建一個鏈表的新節(jié)點struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));// 節(jié)點初始化pnew->number = rand() % 1000;// 節(jié)點的連接, 添加到鏈表的頭部, 新節(jié)點就新的頭結(jié)點pnew->next = head;// head指針前移head = pnew;printf("+++producer, number = %d, tid = %ld\n", pnew->number, pthread_self());pthread_mutex_unlock(&mutex);// 生產(chǎn)了任務(wù), 通知消費者消費pthread_cond_broadcast(&cond);// 生產(chǎn)慢一點sleep(rand() % 3);}return NULL;
}
// 消費者的回調(diào)函數(shù)
void* consumer(void* arg)
{while(1){pthread_mutex_lock(&mutex);// 一直消費, 刪除鏈表中的一個節(jié)點while(head == NULL) // 這樣寫有bug// while(head == NULL){// 任務(wù)隊列, 也就是鏈表中已經(jīng)沒有節(jié)點可以消費了// 消費者線程需要阻塞// 線程加互斥鎖成功, 但是線程阻塞在這行代碼上, 鎖還沒解開// 其他線程在訪問這把鎖的時候也會阻塞, 生產(chǎn)者也會阻塞 ==> 死鎖// 這函數(shù)會自動將線程擁有的鎖解開 >>>>>>>>>>>>>>>>> pthread_cond_wait(&cond, &mutex);// 當(dāng)消費者線程解除阻塞之后, 會自動將這把鎖鎖上// 這時候當(dāng)前這個線程又重新?lián)碛辛诉@把互斥鎖}// 取出鏈表的頭結(jié)點, 將其刪除struct Node* pnode = head;printf("--consumer: number: %d, tid = %ld\n", pnode->number, pthread_self());head = pnode->next;free(pnode);pthread_mutex_unlock(&mutex);sleep(rand() % 3);}return NULL;
}int main()
{// 初始化條件變量pthread_cond_init(&cond, NULL);pthread_mutex_init(&mutex, NULL);// 創(chuàng)建5個生產(chǎn)者, 5個消費者pthread_t ptid[5];pthread_t ctid[5];for(int i=0; i<5; ++i){pthread_create(&ptid[i], NULL, producer, NULL);}for(int i=0; i<5; ++i){pthread_create(&ctid[i], NULL, consumer, NULL);}// 釋放資源for(int i=0; i<5; ++i){// 阻塞等待子線程退出pthread_join(ptid[i], NULL);}for(int i=0; i<5; ++i){pthread_join(ctid[i], NULL);}// 銷毀條件變量pthread_cond_destroy(&cond);pthread_mutex_destroy(&mutex);return 0;
}
問題解答
pthread_cond_wait(&cond, &mutex) 會在執(zhí)行時釋放鎖,這是關(guān)鍵所在。
其它被這個消費者互斥鎖阻塞的線程再這之后就會開始搶鎖
消費者搶鎖的操作是再pthread_cond_wait函數(shù)內(nèi)部實現(xiàn)的
案例分析:前一個消費者消費完了鏈表為空,假設(shè)后面還有一個消費者被這個消費者阻塞后,此時生產(chǎn)者生產(chǎn)數(shù)據(jù)并調(diào)用 pthread_cond_broadcast(&cond) 喚醒所有等待的線程,由于前面一個消費者又一次消費完了,那個阻塞的消費者沒有進行循環(huán)判斷就接著消費就會發(fā)生段錯誤。
加強版生產(chǎn)者消費者
生產(chǎn)者設(shè)置上限,消費者設(shè)置下限,并且用兩個條件變量
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>// 鏈表的節(jié)點
struct Node {int number;struct Node* next;
};// 定義兩個條件變量, 分別控制生產(chǎn)者和消費者
pthread_cond_t cond_producer; // 生產(chǎn)者的條件變量
pthread_cond_t cond_consumer; // 消費者的條件變量
// 互斥鎖變量
pthread_mutex_t mutex;
// 指向頭結(jié)點的指針
struct Node * head = NULL;
// 生產(chǎn)者生產(chǎn)的上限
#define MAX_PRODUCE 5
// 生產(chǎn)者生產(chǎn)的計數(shù)器
int produced_count = 0;// 生產(chǎn)者的回調(diào)函數(shù)
void* producer(void* arg)
{while (1) {pthread_mutex_lock(&mutex);// 如果已經(jīng)生產(chǎn)了 5 個任務(wù),則停止生產(chǎn)if (produced_count >= MAX_PRODUCE) {pthread_mutex_unlock(&mutex);break; // 達到生產(chǎn)上限,退出生產(chǎn)}// 創(chuàng)建一個鏈表的新節(jié)點struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));// 節(jié)點初始化pnew->number = rand() % 1000;// 節(jié)點的連接, 添加到鏈表的頭部, 新節(jié)點就新的頭結(jié)點pnew->next = head;// head指針前移head = pnew;produced_count++; // 更新生產(chǎn)計數(shù)器printf("+++producer, number = %d, tid = %ld\n", pnew->number, pthread_self());// 生產(chǎn)了任務(wù), 通知消費者消費pthread_cond_signal(&cond_consumer);pthread_mutex_unlock(&mutex);// 生產(chǎn)慢一點sleep(rand() % 3);}return NULL;
}// 消費者的回調(diào)函數(shù)
void* consumer(void* arg)
{while (1) {pthread_mutex_lock(&mutex);// 一直消費, 刪除鏈表中的一個節(jié)點while (head == NULL) {// 如果鏈表為空, 消費者線程等待生產(chǎn)者的通知pthread_cond_wait(&cond_consumer, &mutex);}// 取出鏈表的頭結(jié)點, 將其刪除struct Node* pnode = head;printf("--consumer: number: %d, tid = %ld\n", pnode->number, pthread_self());head = pnode->next;free(pnode);pthread_mutex_unlock(&mutex);sleep(rand() % 3);}return NULL;
}int main()
{// 初始化條件變量pthread_cond_init(&cond_producer, NULL);pthread_cond_init(&cond_consumer, NULL);pthread_mutex_init(&mutex, NULL);// 創(chuàng)建5個生產(chǎn)者, 5個消費者pthread_t ptid[5];pthread_t ctid[5];for (int i = 0; i < 5; ++i) {pthread_create(&ptid[i], NULL, producer, NULL);}for (int i = 0; i < 5; ++i) {pthread_create(&ctid[i], NULL, consumer, NULL);}// 阻塞等待線程退出for (int i = 0; i < 5; ++i) {pthread_join(ptid[i], NULL);}for (int i = 0; i < 5; ++i) {pthread_join(ctid[i], NULL);}// 銷毀條件變量pthread_cond_destroy(&cond_producer);pthread_cond_destroy(&cond_consumer);pthread_mutex_destroy(&mutex);return 0;
}
總結(jié)
不使用條件變量的生產(chǎn)者-消費者模型和使用條件變量的生產(chǎn)者-消費者模型
資源占用:使用條件變量的模型更高效,避免了不必要的 CPU 占用。
延遲:條件變量能更快地響應(yīng)條件變化,不會因輪詢間隔導(dǎo)致延遲。
代碼復(fù)雜度:使用條件變量需要額外的代碼來管理條件的等待和喚醒,但能提高性能。
信號量
信號量用在多線程多任務(wù)同步的,一個線程完成了某一個動作就通過信號量告訴別的線程,別的線程再進行某些動作。
信號量實現(xiàn)生產(chǎn)者消費者同步–>一個資源
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
// 鏈表的節(jié)點
struct Node
{int number;struct Node* next;
};
//信號量
sem_t semp;
sem_t semc;struct Node * head = NULL;void* producer(void* arg)
{while(1){sem_wait(&semp);struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));pnew->number = rand() % 1000;pnew->next = head;head = pnew;printf("+++producer, number = %d, tid = %ld\n", pnew->number, pthread_self());sem_post(&semc);sleep(rand() % 3);}return NULL;
}void* consumer(void* arg)
{while(1){sem_wait(&semc);struct Node* pnode = head;printf("--consumer: number: %d, tid = %ld\n", pnode->number, pthread_self());head = pnode->next;free(pnode);sem_post(&semp);sleep(rand() %3);}return NULL;
}
int main()
{//生產(chǎn)者sem_init(&semp,0,1);//資源只有一個,雖然線程很多但是都是被阻塞的//消費者 --> 資源初始化為0 sem_init(&semc,0,0);pthread_t ptid[5];pthread_t ctid[5];for(int i=0; i<5; ++i){pthread_create(&ptid[i], NULL, producer, NULL);}for(int i=0; i<5; ++i){pthread_create(&ctid[i], NULL, consumer, NULL);}// 釋放資源for(int i=0; i<5; ++i){// 阻塞等待子線程退出pthread_join(ptid[i], NULL);}for(int i=0; i<5; ++i){pthread_join(ctid[i], NULL);}sem_destroy(&semp);sem_destroy(&semc);return 0;
}
信號量實現(xiàn)生產(chǎn)者消費者同步–>多個資源
假設(shè)資源數(shù)是>1的,此時如果多個生產(chǎn)者同時對鏈表進行添加數(shù)據(jù)這樣是有問題的!
因此要通過鎖來讓這些線程線性執(zhí)行
上面這種情況消費者是不能夠通知消費者去生產(chǎn)的,此時生產(chǎn)者由于消費者都沒通知所有生產(chǎn)者也阻塞了,這樣的話就沒有任何一個線程在工作–>問題大了!
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
// 鏈表的節(jié)點
struct Node
{int number;struct Node* next;
};
//信號量
sem_t semp;
sem_t semc;
pthread_mutex_t mutex;struct Node * head = NULL;void* producer(void* arg)
{while(1){sem_wait(&semp);pthread_mutex_lock(&mutex);struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));pnew->number = rand() % 1000;pnew->next = head;head = pnew;printf("+++producer, number = %d, tid = %ld\n", pnew->number, pthread_self());pthread_mutex_lock(&mutex);sem_post(&semc);sleep(rand() % 3);}return NULL;
}void* consumer(void* arg)
{while(1){sem_wait(&semc);pthread_mutex_lock(&mutex);struct Node* pnode = head;printf("--consumer: number: %d, tid = %ld\n", pnode->number, pthread_self());head = pnode->next;free(pnode);pthread_mutex_lock(&mutex);sem_post(&semp);sleep(rand() %3);}return NULL;
}int main()
{//生產(chǎn)者sem_init(&semp,0,5);//資源只有一個,雖然線程很多但是都是被阻塞的//消費者 --> 資源初始化為0 sem_init(&semc,0,0);pthread_mutex_init(&mutex, NULL);pthread_t ptid[5];pthread_t ctid[5];for(int i=0; i<5; ++i){pthread_create(&ptid[i], NULL, producer, NULL);}for(int i=0; i<5; ++i){pthread_create(&ctid[i], NULL, consumer, NULL);}// 釋放資源for(int i=0; i<5; ++i){// 阻塞等待子線程退出pthread_join(ptid[i], NULL);}for(int i=0; i<5; ++i){pthread_join(ctid[i], NULL);}sem_destroy(&semp);sem_destroy(&semc);pthread_mutex_destroy(&mutex);return 0;
}
在生產(chǎn)者-消費者模型中會出現(xiàn)“解鎖和通知消費者之間有生產(chǎn)者加鎖”的情況