欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C/C++?pthread線程庫使用示例詳解

 更新時(shí)間:2024年05月30日 11:50:58   作者:Che3rry  
這篇文章主要介紹了C/C++?pthread線程庫使用示例詳解,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧

在進(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)文章

最新評(píng)論