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