C/C++?pthread線程庫(kù)使用示例詳解
在進(jìn)入代碼實(shí)踐之前,我們應(yīng)該搞清楚。
線程是成語(yǔ)的最小執(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寫(xiě)入到該指針指向的內(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í)間片,誰(shuí)搶到誰(shuí)干活,所以完全有可能子線程還沒(méi)有搶到資源,主線程結(jié)束,那么整個(gè)進(jìn)程就結(jié)束了,子線程根本就來(lái)不及干活。
我們這里也可以使用信號(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)槲覀僀++中的沒(méi)有這個(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é)束。
要注意的是:
即使主線程通過(guò)調(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)在我們來(lái)系統(tǒng)描述一下針對(duì)回收子線程數(shù)據(jù)的線程回收技術(shù)吧!
使用主線程棧
在上面子線程調(diào)用退出函數(shù)部分,我們就是使用的主線程棧上的數(shù)據(jù),傳遞給子線程處理該數(shù)據(jù),然后我們主線程在干自己的任務(wù),把修改data數(shù)據(jù)的任務(wù)交給了子線程,最后阻塞在pthread_join()檢查子線程活干的咋樣。
使用子線程堆區(qū)
你覺(jué)得可以使用子線程棧區(qū)的數(shù)據(jù)然后回傳嗎?肯定是不行的,因?yàn)闂^(qū)數(shù)據(jù)在線程退出后會(huì)被銷毀。子線程返回的指針將指向一個(gè)無(wú)效的內(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ù)邏輯,把寫(xiě)一個(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;
}使用全局變量
在文章開(kāi)篇我們就說(shuō)過(guò),主線程和子線程是共享.text、.rodata、.data、.heap、.bss和文件描述符的。所以子線程操作全局變量,然后把修改好的值傳回給主線程當(dāng)然也是允許的,具體實(shí)驗(yàn)請(qǐng)讀者自己設(shè)計(jì)一個(gè)吧
4.線程分離
之前我們說(shuō)過(guò) pthread_join() 是一個(gè)阻塞函數(shù),只要子線程不退出主線程會(huì)被一直阻塞,但是主線程有自己的業(yè)務(wù)邏輯要去執(zhí)行,那應(yīng)該怎么辦呢?
這就涉及到我們的線程分離函數(shù)pthread_detach()上場(chǎng)了。
調(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ù)一般用在什么情況下?
簡(jiǎn)單的后臺(tái)任務(wù)
當(dāng)子線程執(zhí)行的是一個(gè)簡(jiǎn)單的、短暫的后臺(tái)任務(wù),而主線程不需要等待該子線程完成,也不需要獲取子線程的返回值時(shí),線程分離技術(shù)可以很方便地使用。長(zhǎng)期運(yùn)行的任務(wù)
當(dāng)子線程需要執(zhí)行一個(gè)長(zhǎng)期運(yùn)行的任務(wù),而主線程不需要等待它完成,這種情況下也可以使用線程分離。這樣主線程可以繼續(xù)執(zhí)行其他任務(wù),而不必被子線程的運(yùn)行時(shí)間所阻礙。不可預(yù)測(cè)的結(jié)束時(shí)間
當(dāng)子線程的結(jié)束時(shí)間不可預(yù)測(cè),主線程不能在合理的時(shí)間內(nèi)使用pthread_join等待子線程結(jié)束時(shí),線程分離技術(shù)也很有用。這樣可以避免主線程長(zhǎng)時(shí)間等待,導(dǎo)致資源
5.線程同步(或者叫線程間通信?)
由于線程的運(yùn)行順序是由操作系統(tǒng)的調(diào)度算法決定的,誰(shuí)也不知道哪個(gè)線程先執(zhí)行哪個(gè)后執(zhí)行,所以我們必須使用線程同步技術(shù)來(lái)管理相關(guān)的資源。
所謂的同步并不是多個(gè)線程同時(shí)對(duì)內(nèi)存進(jìn)行訪問(wèn),而是按照先后順序依次進(jìn)行的。
每一個(gè)環(huán)節(jié)我都會(huì)給定一個(gè)題目,先給出實(shí)現(xiàn)代碼,隨后講解相關(guān)的知識(shí)。
互斥鎖
互斥鎖就不贅述了,主要就是對(duì)于一個(gè)共享資源必須加鎖,不然有可能出現(xiàn)資源錯(cuò)亂的問(wèn)題。
#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;
}它的用法也比較簡(jiǎn)單,首先想要使用互斥鎖必須先完成初始化,pthread_mutex_init()的第二個(gè)參數(shù)表示互斥鎖屬性,一般寫(xiě)NULL。
使用完之后記得銷毀,銷毀時(shí)傳入的是互斥鎖所在的地址,在調(diào)用的時(shí)候也是傳入地址。
讀寫(xiě)鎖
讀寫(xiě)鎖允許多個(gè)線程同時(shí)獲取讀鎖(只要沒(méi)有線程持有寫(xiě)鎖),但寫(xiě)鎖是排他的,其他線程必須等待寫(xiě)鎖釋放后才能獲取讀鎖或?qū)戞i。
示例代碼如下:我們定義兩個(gè)讀線程,一個(gè)寫(xiě)線程。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
// 定義一個(gè)讀寫(xiě)鎖
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;
}
// 寫(xiě)入共享數(shù)據(jù)的線程函數(shù)
void* writer(void* arg) {
(void)arg; // 未使用的參數(shù)
// 寫(xiě)入鎖
pthread_rwlock_wrlock(&rwlock);
// 修改共享數(shù)據(jù)
shared_data++;
printf("Writer: updated shared_data to %d\n", shared_data);
// 釋放寫(xiě)入鎖
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)建寫(xiě)入者線程
pthread_create(&w1, NULL, writer, NULL);
// 等待寫(xiě)入者線程完成
pthread_join(w1, NULL);
// 銷毀讀寫(xiě)鎖
pthread_rwlock_destroy(&rwlock);
return 0;
}它的使用和互斥鎖是一模一樣的,值不過(guò)多了讀取鎖和寫(xiě)入鎖的調(diào)用,釋放鎖都是一樣的:
// 讀取鎖 pthread_rwlock_rdlock(&rwlock); // 寫(xiě)入鎖 pthread_rwlock_wrlock(&rwlock); //釋放讀取鎖或者寫(xiě)入鎖 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 操作來(lái)喚醒它 //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ì)列有超過(guò)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)過(guò)程
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)作就通過(guò)信號(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)量主要用來(lái)阻塞線程,不能保證線程安全,如果要保證線程安全,需要信號(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í)阻塞:就算被阻塞了,超過(guò)某時(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è)簡(jiǎn)單的使用案例:
該代碼可以清晰查看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í)訪問(wèn)某個(gè)特定的資源或代碼段。同步:協(xié)調(diào)多個(gè)線程的執(zhí)行順序,確保它們按正確的順序執(zhí)行。限制資源的并發(fā)訪問(wèn)數(shù)量:控制同時(shí)訪問(wèn)某些資源(如數(shù)據(jù)庫(kù)連接、文件句柄等)的線程數(shù)量。線程池管理:管理線程池中的線程數(shù)量,以及任務(wù)隊(duì)列中的待處理任務(wù)數(shù)量。
信號(hào)量實(shí)現(xiàn)生產(chǎn)者、消費(fèi)者模型
場(chǎng)景描述:使用信號(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í)訪問(wèn)共享資源的情況,不管生產(chǎn)者和消費(fèi)者線程有多少個(gè),它們都是順序執(zhí)行的。
主要執(zhí)行的邏輯就是,定義生產(chǎn)者信號(hào)量和消費(fèi)者信號(hào)量?jī)蓚€(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)過(guò)程
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è)很大的問(wèn)題,就是可能出現(xiàn)連續(xù)多個(gè)生產(chǎn)者生產(chǎn),這是不應(yīng)該發(fā)生的。這是為什么呢?百思不得其解。
總資源數(shù)大于1
如果生產(chǎn)者和消費(fèi)者線程使用的信號(hào)量對(duì)應(yīng)的總資源數(shù)為大于1,這種場(chǎng)景下出現(xiàn)的情況就比較多了:
- 多個(gè)生產(chǎn)者線程同時(shí)生產(chǎn)
- 多個(gè)消費(fèi)者同時(shí)消費(fèi)
- 生產(chǎn)者線程和消費(fèi)者線程同時(shí)生產(chǎn)和消費(fèi)
所以說(shuō)這個(gè)時(shí)候就會(huì)產(chǎn)生數(shù)據(jù)競(jìng)爭(zhēng)了
#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線程庫(kù) 使用的文章就介紹到這了,更多相關(guān)C++ pthread線程庫(kù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
c++讀取數(shù)據(jù)文件到數(shù)組的實(shí)例
今天小編就為大家分享一篇c++讀取數(shù)據(jù)文件到數(shù)組的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-07-07
C語(yǔ)言調(diào)試手段:鎖定錯(cuò)誤的實(shí)現(xiàn)方法
本篇文章是對(duì)在C語(yǔ)言調(diào)試中,鎖定錯(cuò)誤的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
如何用C++制作LeetCode刷題小技巧-錯(cuò)題記錄本
這篇文章主要介紹了如何用C++制作LeetCode刷題小技巧-錯(cuò)題記錄本的方法,需要的朋友可以參考下2021-04-04
C語(yǔ)言可變參數(shù)與函數(shù)參數(shù)的內(nèi)存對(duì)齊詳解
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言可變參數(shù)與函數(shù)參數(shù)的內(nèi)存對(duì)齊,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03
C++使用適配器模式模擬實(shí)現(xiàn)棧和隊(duì)列
不論是C語(yǔ)言還是C++,我們都用其對(duì)應(yīng)的傳統(tǒng)寫(xiě)法對(duì)棧和隊(duì)列進(jìn)行了模擬實(shí)現(xiàn),現(xiàn)在我們要用新的方法模擬實(shí)現(xiàn)棧和隊(duì)列,這個(gè)新方法就是適配器模式,文章通過(guò)代碼示例和圖文介紹的非常詳細(xì),需要的朋友可以參考下2024-12-12
用c語(yǔ)言實(shí)現(xiàn)2000內(nèi)既能被3整除又能被7整除的個(gè)數(shù)
本篇文章是對(duì)使用c語(yǔ)言實(shí)現(xiàn)2000內(nèi)既能被3整除又能被7整除的個(gè)數(shù),用實(shí)例進(jìn)行了分析說(shuō)明,需要的朋友參考下2013-05-05
C 語(yǔ)言實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 web 服務(wù)器的原理解析
這篇文章主要介紹了C 語(yǔ)言實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 web 服務(wù)器的原理解析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11

