博客 / 詳情

返回

每日一個C++知識點|多線程基礎

多線程開發場景基本是每一個C++開發工程師無法避免的場景,今天就帶大家從零基礎入門C++多線程編程,掌握其中的基礎用法、鎖管理工具和條件變量的內容

多線程的認識

多線程就是一個程序內部運行多個任務,每個任務就是一個線程,充分利用CPU的資源,提高效率的技術

在多個線程中,作為程序入口的線程稱為主線程,由主線程創建負責獨立執行細分任務的線程稱為子線程

實現依賴

在C++中想使用多線程技術,就要引入頭文件<thread>

#include <thread>

線程創建基本流程

首先要定義線程要執行的任務函數,然後要通過std::thread實例化線程對象並且綁定該任務函數,最後調用join()函數或者detach()函數

其中join()來阻塞主線程,等待子線程執行完畢;detach()來將主線程和子線程分離

代碼示例如下

#include <iostream>
#include <thread>

// 子線程任務函數
void task(int num) {
    cout << "子線程執行任務,傳入參數:" << num << endl;
}

int main() {
    // 實例化thread對象並綁定任務,傳入參數5
    std::thread t(task, 5);
    
    // 阻塞主線程,等待子線程執行完成
    t.join();
    
    std::cout << "主線程繼續執行" << std::endl;
    return 0;
}

為什麼要調用join(detach()函數呢,如果不調用線程在對象析構時會觸發程序異常終止

互斥鎖

當多個線程訪問同一塊共享內存時,會出現數據讀寫混亂的情況,這就是競態條件,為了解決這個問題,可以採用互斥鎖std::mutex

std::mutex的核心接口是\
lock(),unlock(),try_lock()

lock()給互斥鎖加鎖,若鎖已被其他線程持有,則當前線程會阻塞,直到獲取到鎖,必須確保只有持unlock()是釋放鎖,確保只有持有鎖的線程才能調用,try_lock()是嘗試加鎖,成功返回true,失敗返回false,不會阻塞當前線程

代碼示例

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;  // 定義全局互斥鎖
int shared_counter = 0;  // 共享資源

// 子線程任務:對共享計數器進行累加
void increment_counter() {
    for (int i = 0; i < 1000; i++) {
        mtx.lock();  // 加鎖,進入臨界區
        shared_counter++;
        mtx.unlock();  // 解鎖,退出臨界區
    }
}

int main() {
    std::thread t1(increment_counter);
    std::thread t2(increment_counter);

    t1.join();
    t2.join();

    std::cout << "最終計數器值:" << shared_counter << std::endl;  // 預期輸出2000
    return 0;
}

互斥鎖雖然可以解決競態條件問題,但需要需手動調用lock()unlock(),若臨界區代碼拋出異常,會導致unlock()無法執行,進而引發死鎖,而且多鎖場景下,若加鎖順序不一致,極易出現死鎖問題,操作難度太大,因而就有了讓互斥鎖更安全靈活的鎖管理工具

鎖管理工具

鎖管理工具主要有三種:分別是守衞鎖,唯一鎖,作用域鎖

std::lock_guard(守衞鎖)

std::lock_guard是最輕量化的RAII 鎖管理工具,構造時自動調用lock()加鎖,析構時自動調用unlock()解鎖

適用於簡單的臨界區資源保護,無需手動控制加解鎖時機,以下是代碼示例:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int shared_num = 0;

void add_num() {
    // 構造lock_guard時自動加鎖
    std::lock_guard<std::mutex> lock(mtx);
    shared_num++;
    std::cout << "當前共享變量值:" << shared_num << std::endl;
    // 函數結束,lock_guard析構自動解鎖,即使中途拋異常也能正常解鎖
}

int main() {
    std::thread t1(add_num);
    std::thread t2(add_num);
    t1.join();
    t2.join();
    return 0;
}

std::unique_lock(唯一鎖)

std::unique_lock是最靈活的 RAII 鎖管理工具,支持手動加解鎖、超時等待、延遲加鎖等操作,並且可配合條件變量使用

適用於複雜的鎖控制場景,需要靈活調整加解鎖時機,代碼示例如下:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int count = 0;

void task() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 延遲加鎖,構造時不自動加鎖
    // 手動加鎖
    lock.lock();
    count += 5;
    std::cout << "count更新為:" << count << std::endl;
    // 手動解鎖
    lock.unlock();
    
    // 可再次加鎖
    lock.lock();
    count += 3;
    std::cout << "count最終值:" << count << std::endl;
}

int main() {
    std::thread t(task);
    t.join();
    return 0;
}

std::scoped_lock(作用域鎖)

std::scoped_lock是專為多鎖場景設計的RAII管理工具,能自動對傳入的多個互斥鎖排序加鎖,從根源杜絕多鎖死鎖問題

適用於需要同時獲取多個互斥鎖的場景,代碼示例如下:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx1, mtx2;
int data1 = 0, data2 = 0;

void update_data() {
    // 自動對mtx1、mtx2排序加鎖,避免死鎖
    std::scoped_lock lock(mtx1, mtx2);
    data1++;
    data2++;
    std::cout << "data1=" << data1 << ",data2=" << data2 << std::endl;
}

int main() {
    std::thread t1(update_data);
    std::thread t2(update_data);
    t1.join();
    t2.join();
    return 0;
}

條件變量

std::condition_variable(條件變量)是 C++ 多線程中實現線程阻塞等待的核心工具,它允許線程阻塞至特定條件滿足後再被喚醒,避免了 “忙等” 帶來的 CPU 資源浪費

核心特性

阻塞等待:線程調用wait()後會釋放持有的互斥鎖,進入阻塞狀態,直到被其他線程喚醒

喚醒機制:通過notify_one()喚醒一個等待線程,或notify_all()喚醒所有等待線程

綁定互斥鎖:必須配合std::unique_lock<std::mutex>使用,無法直接搭配其他鎖

虛假喚醒:即使未被主動喚醒,等待線程也可能被喚醒,因此需在循環中檢查條件是否真的滿足

適用於生產者 - 消費者模型:隊列滿或者空時讓對應線程等待,線程池,多線程同步等待某個事件觸發和超時等待場景等場景,代碼示例如下:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>

std::queue<int> task_queue;
std::mutex mtx;
std::condition_variable cv;

// 生產者:往隊列中添加任務
void producer() {
    for (int i = 1; i <= 5; i++) {
        std::unique_lock<std::mutex> lock(mtx);
        task_queue.push(i);
        std::cout << "生產者生產任務:" << i << std::endl;
        lock.unlock();
        // 喚醒一個消費者線程
        cv.notify_one();
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
}

// 消費者:從隊列中取任務執行
void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        // 循環檢查條件,避免虛假喚醒
        cv.wait(lock, []{ return !task_queue.empty(); });
        
        int task = task_queue.front();
        task_queue.pop();
        std::cout << "消費者執行任務:" << task << std::endl;
        lock.unlock();
        
        if (task == 5) break; // 任務執行完畢退出
    }
}

int main() {
    std::thread prod(producer);
    std::thread cons(consumer);
    
    prod.join();
    cons.join();
    return 0;
}

總結

多線程是通過在程序中創建多個線程,並且結合互斥鎖以及鎖管理工具條件變量來實現CPU利用效率的提高,在短時間內跑完多個任務的技術,是我們每個C++程序員繞不開的技術

覺得文章對您有幫助的話可以點贊關注,我將會持續分享高質量的內容~

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.