C/C++?pthread線程庫使用示例詳解
在進入代碼實踐之前,我們應該搞清楚。
線程是成語的最小執(zhí)行單位,進程是操作系統(tǒng)中最小的資源分配單位。
這樣的話我們可以理解以下兩點:
- 同一地址空間中的多個線程獨有的是:每個線程都有屬于自己的棧區(qū)和寄存器(內(nèi)核中管理的),寄存器主要記錄的就是上下文
- 共享的是:.text、.rodata、.data、.heap、.bss、文件描述符
關于線程個數(shù)的確定:
文件IO操作:文件IO對CPU是使用率不高, 因此可以分時復用CPU時間片, 線程的個數(shù) = 2 * CPU核心數(shù) (效率最高)處理復雜的算法(主要是CPU進行運算, 壓力大),線程的個數(shù) = CPU的核心數(shù) (效率最高)
1.線程創(chuàng)建
#include <pthread.h> int pthread_create( pthread_t *thread , const pthread_attr_t *attr , void *(*start_routine) (void *) , void *arg);
我們主要用到的就是第一個和第三個、第四個參數(shù)。
- 第一個參數(shù)如果線程創(chuàng)建成功,線程ID寫入到該指針指向的內(nèi)存
pthread_t itd1; pthread_create(&tid1, ...)
- 第二個參數(shù)是線程屬性,一般為NULL
- 第三個參數(shù)是線程函數(shù),創(chuàng)建出的子線程的處理動作,也就是該函數(shù)在子線程中執(zhí)行
- 第四個參數(shù)作為實參傳遞到 start_routine指針指向的函數(shù)內(nèi)部。可以傳入一個函數(shù)指針等等作為線程的回調(diào)函數(shù)。
代碼練習
#include <iostream> #include <pthread.h> #include <unistd.h> void* working(void* arg) { std::cout << "子線程" << pthread_self() << std::endl; for (int i = 0; i < 3; i++) { std::cout << "chiled say: " << i << std::endl; } } int main () { pthread_t tid; pthread_create(&tid, NULL, working, NULL); sleep(1); //為啥這里一定要睡一會兒? std::cout << "parent say:" << tid << std::endl; return 0; } //輸出: 子線程140470444414528 chiled say: 0 chiled say: 1 chiled say: 2 parent say:140470444414528
為什么主線程要sleep(1)呢?
因為主線程和子線程都是在搶CPU時間片,誰搶到誰干活,所以完全有可能子線程還沒有搶到資源,主線程結(jié)束,那么整個進程就結(jié)束了,子線程根本就來不及干活。
我們這里也可以使用信號量,等子線程執(zhí)行結(jié)束了,通知主線程,這里就涉及到線程間通信,后面會進行詳細講解。
2.線程退出
#include <pthread.h> void pthread_exit(void *retval);
參數(shù)表示線程退出的時候攜帶的數(shù)據(jù),當前子線程的主線程會得到該數(shù)據(jù)。如果不需要使用,指定為NULL(這是重點,因為我們C++中的沒有這個功能)
主線程可以調(diào)用退出函數(shù)退出,但是地址空間不會被釋放。
子線程調(diào)用退出函數(shù)退出,一般目的是帶出一些有價值的數(shù)據(jù)。
主線程調(diào)用退出函數(shù)
#include <pthread.h> #include <stdio.h> #include <unistd.h> void* child_thread(void* arg) { sleep(1); printf("Child thread is running.\n"); // 子線程執(zhí)行一些工作 pthread_exit(NULL); // 正常退出子線程 } int main() { pthread_t tid; // 創(chuàng)建子線程 if (pthread_create(&tid, NULL, child_thread, NULL) != 0) { perror("Failed to create thread"); return 1; } // 主線程立即退出,子線程繼續(xù)運行 printf("Main thread is exiting.\n"); pthread_exit(NULL); return 0; // 這行代碼不會執(zhí)行,因為主線程已經(jīng)退出 }
在這里我們可以發(fā)現(xiàn)主線程在創(chuàng)建子線程后立即退出,而子線程在繼續(xù)執(zhí)行。
但是我們一般不會這樣調(diào)用函數(shù),因為一般認為主線程的退出就代表程序執(zhí)行結(jié)束。
要注意的是:
即使主線程通過調(diào)用 pthread_exit 退出,子線程也不會變成新的主線程。在 POSIX 線程(pthread)模型中,當主線程退出時,它創(chuàng)建的所有子線程仍然繼續(xù)執(zhí)行,直到它們自己結(jié)束或被其他線程終止。
子線程調(diào)用退出函數(shù)
如果子線程退出想往外面?zhèn)鬟f什么參數(shù),也是配合pthread_join()
一起使用,它的作用是等待子線程結(jié)束,并且獲取返回狀態(tài):
#include <pthread.h> #include <stdio.h> #include <stdlib.h> void* child_thread(void* arg) { int* data = (int*)arg; printf("Child thread is processing data.\n"); // 模擬計算 *data = 42; pthread_exit(data); // 子線程結(jié)束,并返回數(shù)據(jù)指針 } int main() { pthread_t tid; int result; // 分配內(nèi)存用于存儲子線程的結(jié)果,該數(shù)據(jù)位于堆上 int* data = (int*)malloc(sizeof(int)); // 創(chuàng)建子線程 pthread_create(&tid, NULL, child_thread, data); //主線程在干自己的任務,把修改data數(shù)據(jù)的任務交給了子線程 // 等待子線程結(jié)束,并獲取返回狀態(tài) pthread_join(tid, (void**)&data); // 檢查子線程的返回值 if (data != NULL) { printf("Child thread returned: %d\n", *data); free(data); } else { printf("Child thread failed to return data.\n"); free(data); } return 0; }
3.線程回收
在剛才我們已經(jīng)初步認識了線程回收函數(shù):pthread_join()
,這個函數(shù)是一個阻塞函數(shù),如果還有子線程在運行,調(diào)用該函數(shù)就會阻塞,子線程退出函數(shù)解除阻塞進行資源的回收,函數(shù)被調(diào)用一次,只能回收一個子線程,如果有多個子線程則需要循環(huán)進行回收。
#include <pthread.h> // 這是一個阻塞函數(shù), 子線程在運行這個函數(shù)就阻塞 // 子線程退出, 函數(shù)解除阻塞, 回收對應的子線程資源, 類似于回收進程使用的函數(shù) wait() int pthread_join(pthread_t thread, void **retval); pthread_join(tid, (void**)&data);
thread: 要被回收的子線程的線程IDretval: 二級指針, 指向一級指針的地址, 這個地址中存儲了pthread_exit() 傳遞出的數(shù)據(jù),如果不需要這個參數(shù),可以指定為NULL
現(xiàn)在我們來系統(tǒng)描述一下針對回收子線程數(shù)據(jù)的線程回收技術(shù)吧!
使用主線程棧
在上面子線程調(diào)用退出函數(shù)部分,我們就是使用的主線程棧上的數(shù)據(jù),傳遞給子線程處理該數(shù)據(jù),然后我們主線程在干自己的任務,把修改data數(shù)據(jù)的任務交給了子線程,最后阻塞在pthread_join()
檢查子線程活干的咋樣。
使用子線程堆區(qū)
你覺得可以使用子線程棧區(qū)的數(shù)據(jù)然后回傳嗎?肯定是不行的,因為棧區(qū)數(shù)據(jù)在線程退出后會被銷毀。子線程返回的指針將指向一個無效的內(nèi)存地址,導致未定義行為。所以我們可以在子線程上堆區(qū)分配內(nèi)存,然后把數(shù)據(jù)交給主線程:
#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string> #include <iostream> void* child_thread(void* arg) { std::string* str = new std::string("hello world"); // 在堆上分配內(nèi)存 pthread_exit((void*)str); // 返回指向堆上字符串的指針 } int main() { pthread_t tid; // 創(chuàng)建子線程 pthread_create(&tid, NULL, child_thread, NULL); void* ptr = nullptr; //主線程執(zhí)行自己的業(yè)務邏輯,把寫一個hello world字符串的任務交給子線程 // 等待子線程結(jié)束,并獲取返回狀態(tài) pthread_join(tid, &ptr); // 將void*指針轉(zhuǎn)換為std::string*指針,并打印字符串 std::string* str_ptr = static_cast<std::string*>(ptr); std::cout << *str_ptr << std::endl; // 釋放堆上分配的內(nèi)存 delete str_ptr; return 0; }
使用全局變量
在文章開篇我們就說過,主線程和子線程是共享.text、.rodata、.data、.heap、.bss和文件描述符的。所以子線程操作全局變量,然后把修改好的值傳回給主線程當然也是允許的,具體實驗請讀者自己設計一個吧
4.線程分離
之前我們說過 pthread_join() 是一個阻塞函數(shù),只要子線程不退出主線程會被一直阻塞,但是主線程有自己的業(yè)務邏輯要去執(zhí)行,那應該怎么辦呢?
這就涉及到我們的線程分離函數(shù)pthread_detach()
上場了。
調(diào)用這個函數(shù)之后指定的子線程就可以和主線程分離,當子線程退出的時候,其占用的內(nèi)核資源就被系統(tǒng)的其他進程接管并回收了。線程分離之后在主線程中使用pthread_join()就回收不到子線程資源了。
其實也就是父子線程各干各的了:
#include <iostream> #include <pthread.h> #include <unistd.h> void* working(void *arg) { for (int i = 0; i < 10; i ++) { std::cout << "child say: " << i << std::endl; } } int main () { pthread_t tid; pthread_create(&tid, NULL, working, NULL); //子線程與主線程分離 pthread_detach(tid); //主線程執(zhí)行自己的邏輯 for (int i = 100; i < 110; i++) { std::cout << "parent say: " << i << std::endl; } std::cout << "task done!!!" << std::endl; return 0; }
線程分離技術(shù)一般用在什么情況下?
簡單的后臺任務
當子線程執(zhí)行的是一個簡單的、短暫的后臺任務,而主線程不需要等待該子線程完成,也不需要獲取子線程的返回值時,線程分離技術(shù)可以很方便地使用。長期運行的任務
當子線程需要執(zhí)行一個長期運行的任務,而主線程不需要等待它完成,這種情況下也可以使用線程分離。這樣主線程可以繼續(xù)執(zhí)行其他任務,而不必被子線程的運行時間所阻礙。不可預測的結(jié)束時間
當子線程的結(jié)束時間不可預測,主線程不能在合理的時間內(nèi)使用pthread_join等待子線程結(jié)束時,線程分離技術(shù)也很有用。這樣可以避免主線程長時間等待,導致資源
5.線程同步(或者叫線程間通信?)
由于線程的運行順序是由操作系統(tǒng)的調(diào)度算法決定的,誰也不知道哪個線程先執(zhí)行哪個后執(zhí)行,所以我們必須使用線程同步技術(shù)來管理相關的資源。
所謂的同步并不是多個線程同時對內(nèi)存進行訪問,而是按照先后順序依次進行的。
每一個環(huán)節(jié)我都會給定一個題目,先給出實現(xiàn)代碼,隨后講解相關的知識。
互斥鎖
互斥鎖就不贅述了,主要就是對于一個共享資源必須加鎖,不然有可能出現(xiàn)資源錯亂的問題。
#include <pthread.h> #include <stdio.h> #include <stdlib.h> // 定義一個互斥鎖 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 共享數(shù)據(jù) int shared_data = 0; // 線程函數(shù) void* thread_function(void* arg) { // 鎖定互斥鎖 pthread_mutex_lock(&mutex); // 對共享數(shù)據(jù)進行操作 shared_data++; // 打印共享數(shù)據(jù) printf("Thread %ld - shared_data: %d\n", pthread_self(), shared_data); // 解鎖互斥鎖 pthread_mutex_unlock(&mutex); return NULL; } int main() { pthread_t tid1, tid2; // 創(chuàng)建兩個線程 pthread_create(&tid1, NULL, thread_function, NULL); pthread_create(&tid2, NULL, thread_function, NULL); // 等待線程結(jié)束 pthread_join(tid1, NULL); pthread_join(tid2, NULL); // 銷毀互斥鎖 pthread_mutex_destroy(&mutex); return 0; }
它的用法也比較簡單,首先想要使用互斥鎖必須先完成初始化,pthread_mutex_init()
的第二個參數(shù)表示互斥鎖屬性,一般寫NULL。
使用完之后記得銷毀,銷毀時傳入的是互斥鎖所在的地址,在調(diào)用的時候也是傳入地址。
讀寫鎖
讀寫鎖允許多個線程同時獲取讀鎖(只要沒有線程持有寫鎖),但寫鎖是排他的,其他線程必須等待寫鎖釋放后才能獲取讀鎖或?qū)戞i。
示例代碼如下:我們定義兩個讀線程,一個寫線程。
#include <pthread.h> #include <stdio.h> #include <stdlib.h> // 定義一個讀寫鎖 pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; // 共享數(shù)據(jù) int shared_data = 0; // 讀取共享數(shù)據(jù)的線程函數(shù) void* reader(void* arg) { (void)arg; // 未使用的參數(shù) // 讀取鎖 pthread_rwlock_rdlock(&rwlock); printf("Reader: shared_data = %d\n", shared_data); // 釋放讀取鎖 pthread_rwlock_unlock(&rwlock); return NULL; } // 寫入共享數(shù)據(jù)的線程函數(shù) void* writer(void* arg) { (void)arg; // 未使用的參數(shù) // 寫入鎖 pthread_rwlock_wrlock(&rwlock); // 修改共享數(shù)據(jù) shared_data++; printf("Writer: updated shared_data to %d\n", shared_data); // 釋放寫入鎖 pthread_rwlock_unlock(&rwlock); return NULL; } int main() { pthread_t r1, r2, w1; // 創(chuàng)建讀者線程 pthread_create(&r1, NULL, reader, NULL); // 創(chuàng)建另一個讀者線程 pthread_create(&r2, NULL, reader, NULL); // 等待讀者線程完成 pthread_join(r1, NULL); pthread_join(r2, NULL); // 創(chuàng)建寫入者線程 pthread_create(&w1, NULL, writer, NULL); // 等待寫入者線程完成 pthread_join(w1, NULL); // 銷毀讀寫鎖 pthread_rwlock_destroy(&rwlock); return 0; }
它的使用和互斥鎖是一模一樣的,值不過多了讀取鎖和寫入鎖的調(diào)用,釋放鎖都是一樣的:
// 讀取鎖 pthread_rwlock_rdlock(&rwlock); // 寫入鎖 pthread_rwlock_wrlock(&rwlock); //釋放讀取鎖或者寫入鎖 pthread_rwlock_unlock(&rwlock);
條件變量
學完條件變量,我們就可以實現(xiàn)所謂的“線程依次執(zhí)行”。
整個使用方法如下:
#include <pthread.h> //定義條件變量類型變量 pthread_cond_t cond; //初始化 //第一個傳參&cond //第二個參數(shù)為條件變量屬性,一般使用默認屬性,指定為NULL int pthread_cond_init(pthread_cond_t *cond, NULL) //釋放資源 int pthread_cond_destroy(pthread_cond_t *cond); //線程阻塞函數(shù):它的工作流程如下 //1. 釋放與條件變量cond關聯(lián)的互斥鎖mutex //2. 之后,調(diào)用線程會被阻塞,并從運行狀態(tài)中移除,進入等待條件變量的狀態(tài)。 //3. 直到另一個線程執(zhí)行了對應的 pthread_cond_signal 或 pthread_cond_broadcast 操作來喚醒它 //4. 被喚醒后重新獲取互斥鎖 //5.解除阻塞 int pthread_cond_wait(pthread_cond_t *restrict cond , pthread_mutex_t *restrict mutex); //有超時時間的線程阻塞函數(shù),時間到達之后,解除阻塞 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);
這里的案例就使用我們經(jīng)典的生產(chǎn)者單消費者模型
這里有三個生產(chǎn)者、三個消費者,生產(chǎn)者只生產(chǎn)50個商品,如果當前生產(chǎn)者發(fā)現(xiàn)任務隊列有超過10個商品,生產(chǎn)者休息,如果消費者消費完了,消費者阻塞,通知生產(chǎn)者生產(chǎn),生產(chǎn)者生產(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; void* producer(void *arg) { while(1) { //模擬生產(chǎn)時間 sleep(rand() % 3); pthread_mutex_lock(&mutex); Node* pnew = (struct Node*)malloc(sizeof(Node)); pnew->number = rand() % 1000; pnew->next = head; head = pnew; printf("producer, number = %d, tid=%ld\n" , pnew->number , pthread_self()); pthread_mutex_unlock(&mutex); //生產(chǎn)了任務,通知消費者消費 pthread_cond_broadcast(&cond); } return nullptr; } void* consumer(void *arg) { while(1) { pthread_mutex_lock(&mutex); while(head == nullptr) { pthread_cond_wait(&cond, &mutex); } //消費過程 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 nullptr; } int main() { pthread_cond_init(&cond, nullptr); pthread_mutex_init(&mutex, nullptr); //創(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], nullptr, producer, nullptr); } for (int i = 0; i < 5; i++) { pthread_create(&ptid[i], nullptr, consumer, nullptr); } //釋放資源 for (int i = 0; i < 5; i++) { pthread_join(ptid[i], nullptr); } for (int i = 0; i < 5; i++) { pthread_join(ctid[i], nullptr); } //銷毀互斥鎖和條件變量 pthread_cond_destroy(&cond); pthread_mutex_destroy(&mutex); }
信號量
信號量用在多線程多任務同步的,一個線程完成了某一個動作就通過信號量告訴別的線程,別的線程再進行某些動作。信號量不一定是鎖定某一個資源,而是流程上的概念,比如:有A,B兩個線程,B線程要等A線程完成某一任務以后再進行自己下面的步驟,這個任務并不一定是鎖定某一資源,還可以是進行一些計算或者數(shù)據(jù)處理之類。
強調(diào)?。。?/strong>
信號量主要用來阻塞線程,不能保證線程安全,如果要保證線程安全,需要信號量和互斥鎖一起使用!
如果五個線程同時被阻塞在sem_wait(&sem)
,有一個線程調(diào)用了sem_post(&sem)
,很可能多個線程同時解除阻塞!
#include <semaphore.h> //定義變量 sem_t sem; //初始化 // pshared = 0 線程同步 // pshared 非 0 進程同步 // value:初始化當前信號量擁有的資源數(shù)(>=0),如果資源數(shù)為0,線程就會被阻塞了。 int sem_init(sem_t *sem, int pshared, unsighed int val); //釋放資源 int sem_destroy(sem_t *sem); //線程阻塞函數(shù):如果資源數(shù)被耗盡,則函數(shù)阻塞 // 函數(shù)被調(diào)用, sem中的資源就會被消耗1個, 資源數(shù)-1 int sem_wait(sem_t *sem); //如果資源被耗盡,直接返回錯誤號,用于處理獲取資源失敗之后的情況 int sem_trywait(sem_t *sem); //超時阻塞:就算被阻塞了,超過某時間解除阻塞 int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); //調(diào)用該函數(shù)給sem中的資源數(shù)+1 int sem_post(sem_t *sem);
這里給一個簡單的使用案例:
該代碼可以清晰查看sem_wait和sem_post的行為
#include <pthread.h> #include <semaphore.h> #include <stdio.h> #include <unistd.h> #define MAXNUM 2 sem_t semPtr; pthread_t a_thread, b_thread, c_thread; int g_phreadNum = 1; void *func1(void *arg) { sem_wait(&semPtr); printf("a_thread get a semaphore \n"); sleep(5); sem_post(&semPtr); printf("a_thread release semaphore \n"); } void *func2(void *arg) { sem_wait(&semPtr); printf("b_thread get a semaphore \n"); sleep(5); sem_post(&semPtr); printf("b_thread release semaphore \n"); } void *func3(void *arg) { sem_wait(&semPtr); printf("c_thread get a semaphore \n"); sleep(5); sem_post(&semPtr); printf("c_thread release semaphore \n"); } int main() { int taskNum; // 創(chuàng)建2個信號量 sem_init(&semPtr, 0, MAXNUM); //線程1獲取1個信號量,5秒后釋放 pthread_create(&a_thread, NULL, func1, NULL); //線程2獲取1個信號量,5秒后釋放 pthread_create(&b_thread, NULL, func2, NULL); sleep(1); //線程3獲取信號量,只有線程1或者線程2釋放后,才能獲取到 pthread_create(&c_thread, NULL, func3, NULL); sleep(10); //銷毀信號量 sem_destroy(&semPtr); return 0; }
互斥鎖:防止多個線程同時訪問某個特定的資源或代碼段。同步:協(xié)調(diào)多個線程的執(zhí)行順序,確保它們按正確的順序執(zhí)行。限制資源的并發(fā)訪問數(shù)量:控制同時訪問某些資源(如數(shù)據(jù)庫連接、文件句柄等)的線程數(shù)量。線程池管理:管理線程池中的線程數(shù)量,以及任務隊列中的待處理任務數(shù)量。
信號量實現(xiàn)生產(chǎn)者、消費者模型
場景描述:使用信號量實現(xiàn)生產(chǎn)者和消費者模型,生產(chǎn)者有5個,往鏈表頭部添加節(jié)點,消費者也有5個,刪除鏈表頭部的節(jié)點。
總資源數(shù)為1
如果生產(chǎn)者和消費者使用的信號量總資源數(shù)為1,那么不會出現(xiàn)生產(chǎn)者線程和消費者線程同時訪問共享資源的情況,不管生產(chǎn)者和消費者線程有多少個,它們都是順序執(zhí)行的。
主要執(zhí)行的邏輯就是,定義生產(chǎn)者信號量和消費者信號量兩個信號量,他們一共只持有1個資源。在生產(chǎn)者生產(chǎn)完之后,給消費者增加一個資源,消費者消費完了給生產(chǎn)者增加一個資源
所以本節(jié)完全可以不使用互斥鎖
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <semaphore.h> #include <pthread.h> // 鏈表的節(jié)點 struct Node { int number; struct Node* next; }; // 生產(chǎn)者線程信號量 sem_t psem; // 消費者線程信號量 sem_t csem; // 指向頭結(jié)點的指針 struct Node * head = NULL; // 生產(chǎn)者的回調(diào)函數(shù) void* producer(void* arg) { // 一直生產(chǎn) while(1) { // 生產(chǎn)者拿一個信號量 sem_wait(&psem); //生產(chǎn)過程 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(&csem); // 生產(chǎn)慢一點 sleep(rand() % 3); } return NULL; } // 消費者的回調(diào)函數(shù) void* consumer(void* arg) { while(1) { sem_wait(&csem); // 取出鏈表的頭結(jié)點, 將其刪除 struct Node* pnode = head; printf("--consumer: number: %d, tid = %ld\n", pnode->number, pthread_self()); head = pnode->next; free(pnode); // 通知生產(chǎn)者生成, 給生產(chǎn)者加信號燈 sem_post(&psem); sleep(rand() % 3); } return NULL; } int main() { // 初始化信號量 // 生產(chǎn)者和消費者擁有的信號燈的總和為1 sem_init(&psem, 0, 1); // 生產(chǎn)者線程一共有1個信號燈 sem_init(&csem, 0, 0); // 消費者線程一共有0個信號燈 // 創(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); } sem_destroy(&psem); sem_destroy(&csem); return 0; }
該代碼有一個很大的問題,就是可能出現(xiàn)連續(xù)多個生產(chǎn)者生產(chǎn),這是不應該發(fā)生的。這是為什么呢?百思不得其解。
總資源數(shù)大于1
如果生產(chǎn)者和消費者線程使用的信號量對應的總資源數(shù)為大于1,這種場景下出現(xiàn)的情況就比較多了:
- 多個生產(chǎn)者線程同時生產(chǎn)
- 多個消費者同時消費
- 生產(chǎn)者線程和消費者線程同時生產(chǎn)和消費
所以說這個時候就會產(chǎn)生數(shù)據(jù)競爭了
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <semaphore.h> #include <pthread.h> // 鏈表的節(jié)點 struct Node { int number; struct Node* next; }; // 生產(chǎn)者線程信號量 sem_t psem; // 消費者線程信號量 sem_t csem; // 互斥鎖變量 pthread_mutex_t mutex; // 指向頭結(jié)點的指針 struct Node * head = NULL; // 生產(chǎn)者的回調(diào)函數(shù) void* producer(void* arg) { // 一直生產(chǎn) while(1) { // 生產(chǎn)者拿一個信號燈 sem_wait(&psem); // 加鎖, 這句代碼放到 sem_wait()上邊, 有可能會造成死鎖 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); // 通知消費者消費 sem_post(&csem); // 生產(chǎn)慢一點 sleep(rand() % 3); } return NULL; } // 消費者的回調(diào)函數(shù) void* consumer(void* arg) { while(1) { sem_wait(&csem); pthread_mutex_lock(&mutex); struct Node* pnode = head; printf("--consumer: number: %d, tid = %ld\n", pnode->number, pthread_self()); head = pnode->next; // 取出鏈表的頭結(jié)點, 將其刪除 free(pnode); pthread_mutex_unlock(&mutex); // 通知生產(chǎn)者生成, 給生產(chǎn)者加信號燈 sem_post(&psem); sleep(rand() % 3); } return NULL; } int main() { // 初始化信號量 sem_init(&psem, 0, 5); // 生成者線程一共有5個信號燈 sem_init(&csem, 0, 0); // 消費者線程一共有0個信號燈 // 初始化互斥鎖 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); } sem_destroy(&psem); sem_destroy(&csem); pthread_mutex_destroy(&mutex); return 0; }
到此這篇關于C/C++ pthread線程庫 使用的文章就介紹到這了,更多相關C++ pthread線程庫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C語言可變參數(shù)與函數(shù)參數(shù)的內(nèi)存對齊詳解
這篇文章主要為大家詳細介紹了C語言可變參數(shù)與函數(shù)參數(shù)的內(nèi)存對齊,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03用c語言實現(xiàn)2000內(nèi)既能被3整除又能被7整除的個數(shù)
本篇文章是對使用c語言實現(xiàn)2000內(nèi)既能被3整除又能被7整除的個數(shù),用實例進行了分析說明,需要的朋友參考下2013-05-05C 語言實現(xiàn)一個簡單的 web 服務器的原理解析
這篇文章主要介紹了C 語言實現(xiàn)一個簡單的 web 服務器的原理解析,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11