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

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

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

在進入代碼實踐之前,我們應該搞清楚。

線程是成語的最小執(zhí)行單位,進程是操作系統(tǒng)中最小的資源分配單位。

這樣的話我們可以理解以下兩點:

  • 同一地址空間中的多個線程獨有的是:每個線程都有屬于自己的棧區(qū)和寄存器(內核中管理的),寄存器主要記錄的就是上下文
  • 共享的是:.text、.rodata、.data、.heap、.bss、文件描述符

關于線程個數的確定:

文件IO操作:文件IO對CPU是使用率不高, 因此可以分時復用CPU時間片, 線程的個數 = 2 * CPU核心數 (效率最高)處理復雜的算法(主要是CPU進行運算, 壓力大),線程的個數 = CPU的核心數 (效率最高)

1.線程創(chuàng)建

#include <pthread.h>
int pthread_create(
	  pthread_t *thread
	, const pthread_attr_t *attr
	, void *(*start_routine) (void *)
	, void *arg);

我們主要用到的就是第一個和第三個、第四個參數。

  • 第一個參數如果線程創(chuàng)建成功,線程ID寫入到該指針指向的內存pthread_t itd1; pthread_create(&tid1, ...)
  • 第二個參數是線程屬性,一般為NULL
  • 第三個參數是線程函數,創(chuàng)建出的子線程的處理動作,也就是該函數在子線程中執(zhí)行
  • 第四個參數作為實參傳遞到 start_routine指針指向的函數內部??梢詡魅胍粋€函數指針等等作為線程的回調函數。

代碼練習

#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時間片,誰搶到誰干活,所以完全有可能子線程還沒有搶到資源,主線程結束,那么整個進程就結束了,子線程根本就來不及干活。

我們這里也可以使用信號量,等子線程執(zhí)行結束了,通知主線程,這里就涉及到線程間通信,后面會進行詳細講解。

2.線程退出

#include <pthread.h>
void pthread_exit(void *retval);

參數表示線程退出的時候攜帶的數據,當前子線程的主線程會得到該數據。如果不需要使用,指定為NULL(這是重點,因為我們C++中的沒有這個功能)

主線程可以調用退出函數退出,但是地址空間不會被釋放。
子線程調用退出函數退出,一般目的是帶出一些有價值的數據。

主線程調用退出函數

#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í)行,因為主線程已經退出
}

在這里我們可以發(fā)現主線程在創(chuàng)建子線程后立即退出,而子線程在繼續(xù)執(zhí)行。
但是我們一般不會這樣調用函數,因為一般認為主線程的退出就代表程序執(zhí)行結束。

要注意的是:
即使主線程通過調用 pthread_exit 退出,子線程也不會變成新的主線程。在 POSIX 線程(pthread)模型中,當主線程退出時,它創(chuàng)建的所有子線程仍然繼續(xù)執(zhí)行,直到它們自己結束或被其他線程終止。

子線程調用退出函數

如果子線程退出想往外面?zhèn)鬟f什么參數,也是配合pthread_join()一起使用,它的作用是等待子線程結束,并且獲取返回狀態(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); // 子線程結束,并返回數據指針
}
int main() {
    pthread_t tid;
    int result;
    // 分配內存用于存儲子線程的結果,該數據位于堆上
    int* data = (int*)malloc(sizeof(int));
    // 創(chuàng)建子線程
    pthread_create(&tid, NULL, child_thread, data);
	//主線程在干自己的任務,把修改data數據的任務交給了子線程
    // 等待子線程結束,并獲取返回狀態(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.線程回收

在剛才我們已經初步認識了線程回收函數:pthread_join(),這個函數是一個阻塞函數,如果還有子線程在運行,調用該函數就會阻塞,子線程退出函數解除阻塞進行資源的回收,函數被調用一次,只能回收一個子線程,如果有多個子線程則需要循環(huán)進行回收。

#include <pthread.h>
// 這是一個阻塞函數, 子線程在運行這個函數就阻塞
// 子線程退出, 函數解除阻塞, 回收對應的子線程資源, 類似于回收進程使用的函數 wait()
int pthread_join(pthread_t thread, void **retval);
pthread_join(tid, (void**)&data);

thread: 要被回收的子線程的線程IDretval: 二級指針, 指向一級指針的地址, 這個地址中存儲了pthread_exit() 傳遞出的數據,如果不需要這個參數,可以指定為NULL

現在我們來系統(tǒng)描述一下針對回收子線程數據的線程回收技術吧!

使用主線程棧

在上面子線程調用退出函數部分,我們就是使用的主線程棧上的數據,傳遞給子線程處理該數據,然后我們主線程在干自己的任務,把修改data數據的任務交給了子線程,最后阻塞在pthread_join()檢查子線程活干的咋樣。

使用子線程堆區(qū)

你覺得可以使用子線程棧區(qū)的數據然后回傳嗎?肯定是不行的,因為棧區(qū)數據在線程退出后會被銷毀。子線程返回的指針將指向一個無效的內存地址,導致未定義行為。所以我們可以在子線程上堆區(qū)分配內存,然后把數據交給主線程:

#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"); // 在堆上分配內存
    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字符串的任務交給子線程
    // 等待子線程結束,并獲取返回狀態(tài)
    pthread_join(tid, &ptr);
    // 將void*指針轉換為std::string*指針,并打印字符串
    std::string* str_ptr = static_cast<std::string*>(ptr);
    std::cout << *str_ptr << std::endl;
    // 釋放堆上分配的內存
    delete str_ptr;
    return 0;
}

使用全局變量

在文章開篇我們就說過,主線程和子線程是共享.text、.rodata、.data、.heap、.bss和文件描述符的。所以子線程操作全局變量,然后把修改好的值傳回給主線程當然也是允許的,具體實驗請讀者自己設計一個吧

4.線程分離

之前我們說過 pthread_join() 是一個阻塞函數,只要子線程不退出主線程會被一直阻塞,但是主線程有自己的業(yè)務邏輯要去執(zhí)行,那應該怎么辦呢?

這就涉及到我們的線程分離函數pthread_detach()上場了。

調用這個函數之后指定的子線程就可以和主線程分離,當子線程退出的時候,其占用的內核資源就被系統(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;
}

線程分離技術一般用在什么情況下?

簡單的后臺任務
當子線程執(zhí)行的是一個簡單的、短暫的后臺任務,而主線程不需要等待該子線程完成,也不需要獲取子線程的返回值時,線程分離技術可以很方便地使用。長期運行的任務
當子線程需要執(zhí)行一個長期運行的任務,而主線程不需要等待它完成,這種情況下也可以使用線程分離。這樣主線程可以繼續(xù)執(zhí)行其他任務,而不必被子線程的運行時間所阻礙。不可預測的結束時間
當子線程的結束時間不可預測,主線程不能在合理的時間內使用pthread_join等待子線程結束時,線程分離技術也很有用。這樣可以避免主線程長時間等待,導致資源

5.線程同步(或者叫線程間通信?)

由于線程的運行順序是由操作系統(tǒng)的調度算法決定的,誰也不知道哪個線程先執(zhí)行哪個后執(zhí)行,所以我們必須使用線程同步技術來管理相關的資源。

所謂的同步并不是多個線程同時對內存進行訪問,而是按照先后順序依次進行的。

每一個環(huán)節(jié)我都會給定一個題目,先給出實現代碼,隨后講解相關的知識。

互斥鎖

互斥鎖就不贅述了,主要就是對于一個共享資源必須加鎖,不然有可能出現資源錯亂的問題。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
// 定義一個互斥鎖
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 共享數據
int shared_data = 0;
// 線程函數
void* thread_function(void* arg) {
    // 鎖定互斥鎖
    pthread_mutex_lock(&mutex);
    // 對共享數據進行操作
    shared_data++;
    // 打印共享數據
    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);
    // 等待線程結束
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    // 銷毀互斥鎖
    pthread_mutex_destroy(&mutex);
    return 0;
}

它的用法也比較簡單,首先想要使用互斥鎖必須先完成初始化,
pthread_mutex_init()的第二個參數表示互斥鎖屬性,一般寫NULL。

使用完之后記得銷毀,銷毀時傳入的是互斥鎖所在的地址,在調用的時候也是傳入地址。

讀寫鎖

讀寫鎖允許多個線程同時獲取讀鎖(只要沒有線程持有寫鎖),但寫鎖是排他的,其他線程必須等待寫鎖釋放后才能獲取讀鎖或寫鎖。

示例代碼如下:我們定義兩個讀線程,一個寫線程。

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
// 定義一個讀寫鎖
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
// 共享數據
int shared_data = 0;
// 讀取共享數據的線程函數
void* reader(void* arg) {
    (void)arg; // 未使用的參數
    // 讀取鎖
    pthread_rwlock_rdlock(&rwlock);
    printf("Reader: shared_data = %d\n", shared_data);
    // 釋放讀取鎖
    pthread_rwlock_unlock(&rwlock);
    return NULL;
}
// 寫入共享數據的線程函數
void* writer(void* arg) {
    (void)arg; // 未使用的參數
    // 寫入鎖
    pthread_rwlock_wrlock(&rwlock);
    // 修改共享數據
    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;
}

它的使用和互斥鎖是一模一樣的,值不過多了讀取鎖和寫入鎖的調用,釋放鎖都是一樣的:

// 讀取鎖
pthread_rwlock_rdlock(&rwlock);
// 寫入鎖
pthread_rwlock_wrlock(&rwlock);
//釋放讀取鎖或者寫入鎖
pthread_rwlock_unlock(&rwlock);

條件變量

學完條件變量,我們就可以實現所謂的“線程依次執(zhí)行”。
整個使用方法如下:

#include <pthread.h>
//定義條件變量類型變量
pthread_cond_t cond;
//初始化
//第一個傳參&cond
//第二個參數為條件變量屬性,一般使用默認屬性,指定為NULL
int pthread_cond_init(pthread_cond_t *cond, NULL) 
//釋放資源
int pthread_cond_destroy(pthread_cond_t *cond);
//線程阻塞函數:它的工作流程如下
//1. 釋放與條件變量cond關聯的互斥鎖mutex
//2. 之后,調用線程會被阻塞,并從運行狀態(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);
//有超時時間的線程阻塞函數,時間到達之后,解除阻塞
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);

這里的案例就使用我們經典的生產者單消費者模型
這里有三個生產者、三個消費者,生產者只生產50個商品,如果當前生產者發(fā)現任務隊列有超過10個商品,生產者休息,如果消費者消費完了,消費者阻塞,通知生產者生產,生產者生產

#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;
// 指向頭結點的指針
struct Node * head = NULL;
void* producer(void *arg) {
    while(1) {
        //模擬生產時間
        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);
        //生產了任務,通知消費者消費
        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個生產者,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線程完成某一任務以后再進行自己下面的步驟,這個任務并不一定是鎖定某一資源,還可以是進行一些計算或者數據處理之類

強調?。。?/strong>

信號量主要用來阻塞線程,不能保證線程安全,如果要保證線程安全,需要信號量和互斥鎖一起使用!

如果五個線程同時被阻塞在sem_wait(&sem),有一個線程調用了sem_post(&sem),很可能多個線程同時解除阻塞!

#include <semaphore.h>
//定義變量
sem_t sem;
//初始化
// pshared = 0 線程同步
// pshared 非 0 進程同步
// value:初始化當前信號量擁有的資源數(>=0),如果資源數為0,線程就會被阻塞了。
int sem_init(sem_t *sem, int pshared, unsighed int val);
//釋放資源
int sem_destroy(sem_t *sem);
//線程阻塞函數:如果資源數被耗盡,則函數阻塞
// 函數被調用, sem中的資源就會被消耗1個, 資源數-1
int sem_wait(sem_t *sem);
//如果資源被耗盡,直接返回錯誤號,用于處理獲取資源失敗之后的情況
int sem_trywait(sem_t *sem);
//超時阻塞:就算被阻塞了,超過某時間解除阻塞
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
//調用該函數給sem中的資源數+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;
}
互斥鎖:防止多個線程同時訪問某個特定的資源或代碼段。同步:協調多個線程的執(zhí)行順序,確保它們按正確的順序執(zhí)行。限制資源的并發(fā)訪問數量:控制同時訪問某些資源(如數據庫連接、文件句柄等)的線程數量。線程池管理:管理線程池中的線程數量,以及任務隊列中的待處理任務數量。

信號量實現生產者、消費者模型

場景描述:使用信號量實現生產者和消費者模型,生產者有5個,往鏈表頭部添加節(jié)點,消費者也有5個,刪除鏈表頭部的節(jié)點。

總資源數為1

如果生產者和消費者使用的信號量總資源數為1,那么不會出現生產者線程和消費者線程同時訪問共享資源的情況,不管生產者和消費者線程有多少個,它們都是順序執(zhí)行的。

主要執(zhí)行的邏輯就是,定義生產者信號量和消費者信號量兩個信號量,他們一共只持有1個資源。在生產者生產完之后,給消費者增加一個資源,消費者消費完了給生產者增加一個資源

所以本節(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;
};
// 生產者線程信號量
sem_t psem;
// 消費者線程信號量
sem_t csem;
// 指向頭結點的指針
struct Node * head = NULL;
// 生產者的回調函數
void* producer(void* arg)
{
    // 一直生產
    while(1)
    {
        // 生產者拿一個信號量
        sem_wait(&psem);
		//生產過程
        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);
        // 生產慢一點
        sleep(rand() % 3);
    }
    return NULL;
}
// 消費者的回調函數
void* consumer(void* arg)
{
    while(1)
    {
        sem_wait(&csem);
        // 取出鏈表的頭結點, 將其刪除
        struct Node* pnode = head;
        printf("--consumer: number: %d, tid = %ld\n", pnode->number, pthread_self());
        head  = pnode->next;
        free(pnode);
        // 通知生產者生成, 給生產者加信號燈
        sem_post(&psem);
        sleep(rand() % 3);
    }
    return NULL;
}
int main()
{
    // 初始化信號量
    // 生產者和消費者擁有的信號燈的總和為1
    sem_init(&psem, 0, 1);  // 生產者線程一共有1個信號燈
    sem_init(&csem, 0, 0);  // 消費者線程一共有0個信號燈
    // 創(chuàng)建5個生產者, 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;
}

該代碼有一個很大的問題,就是可能出現連續(xù)多個生產者生產,這是不應該發(fā)生的。這是為什么呢?百思不得其解。

總資源數大于1

如果生產者和消費者線程使用的信號量對應的總資源數為大于1,這種場景下出現的情況就比較多了:

  • 多個生產者線程同時生產
  • 多個消費者同時消費
  • 生產者線程和消費者線程同時生產和消費

所以說這個時候就會產生數據競爭了

#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;
};
// 生產者線程信號量
sem_t psem;
// 消費者線程信號量
sem_t csem;
// 互斥鎖變量
pthread_mutex_t mutex;
// 指向頭結點的指針
struct Node * head = NULL;
// 生產者的回調函數
void* producer(void* arg)
{
    // 一直生產
    while(1)
    {
        // 生產者拿一個信號燈
        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é)點就新的頭結點
        pnew->next = head;
        // head指針前移
        head = pnew;
        printf("+++producer, number = %d, tid = %ld\n", pnew->number, pthread_self());
        pthread_mutex_unlock(&mutex);
        // 通知消費者消費
        sem_post(&csem);
        // 生產慢一點
        sleep(rand() % 3);
    }
    return NULL;
}
// 消費者的回調函數
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;
        // 取出鏈表的頭結點, 將其刪除
        free(pnode);
        pthread_mutex_unlock(&mutex);
        // 通知生產者生成, 給生產者加信號燈
        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個生產者, 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線程庫內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Qt實現棋盤游戲

    Qt實現棋盤游戲

    這篇文章主要為大家詳細介紹了Qt實現棋盤游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • C語言make和Makefile介紹及使用

    C語言make和Makefile介紹及使用

    這篇文章介紹了C語言make和Makefile以及使用方法,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-01-01
  • c++讀取數據文件到數組的實例

    c++讀取數據文件到數組的實例

    今天小編就為大家分享一篇c++讀取數據文件到數組的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-07-07
  • C語言調試手段:鎖定錯誤的實現方法

    C語言調試手段:鎖定錯誤的實現方法

    本篇文章是對在C語言調試中,鎖定錯誤的方法進行了詳細的分析介紹,需要的朋友參考下
    2013-05-05
  • 如何用C++制作LeetCode刷題小技巧-錯題記錄本

    如何用C++制作LeetCode刷題小技巧-錯題記錄本

    這篇文章主要介紹了如何用C++制作LeetCode刷題小技巧-錯題記錄本的方法,需要的朋友可以參考下
    2021-04-04
  • C語言可變參數與函數參數的內存對齊詳解

    C語言可變參數與函數參數的內存對齊詳解

    這篇文章主要為大家詳細介紹了C語言可變參數與函數參數的內存對齊,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • 一篇文章詳細解釋C++的友元(friend)

    一篇文章詳細解釋C++的友元(friend)

    這篇文章主要為大家詳細介紹了C++的友元(friend),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • C++使用適配器模式模擬實現棧和隊列

    C++使用適配器模式模擬實現棧和隊列

    不論是C語言還是C++,我們都用其對應的傳統(tǒng)寫法對棧和隊列進行了模擬實現,現在我們要用新的方法模擬實現棧和隊列,這個新方法就是適配器模式,文章通過代碼示例和圖文介紹的非常詳細,需要的朋友可以參考下
    2024-12-12
  • 用c語言實現2000內既能被3整除又能被7整除的個數

    用c語言實現2000內既能被3整除又能被7整除的個數

    本篇文章是對使用c語言實現2000內既能被3整除又能被7整除的個數,用實例進行了分析說明,需要的朋友參考下
    2013-05-05
  • C 語言實現一個簡單的 web 服務器的原理解析

    C 語言實現一個簡單的 web 服務器的原理解析

    這篇文章主要介紹了C 語言實現一個簡單的 web 服務器的原理解析,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-11-11

最新評論