成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

玩轉 C++11 多線程:讓你的程序飛起來的 std::thread 終極指南

開發
你還在為 C++ 多線程編程發愁嗎?別擔心,今天咱們就用大白話徹底搞定std::thread!看完這篇,保證你對C++11多線程的理解從"一臉懵逼"變成"原來如此"!

前言:為啥要學多線程?

想象一下,你正在廚房做飯。如果你是單線程工作,那就只能先切菜,切完再炒菜,炒完再煮湯...一項一項按順序來。但現實中的你肯定是多線程操作啊:鍋里炒著菜,同時旁邊的電飯煲在煮飯,熱水壺在燒水,也許你還能同時看看手機...這就是多線程的威力!

在程序世界里,多線程就像多了幾個"分身",可以同時處理不同的任務,充分利用多核CPU的性能,讓程序跑得飛快。特別是現在誰的電腦不是多核啊,不用多線程簡直是浪費資源!

C++11標準終于給我們帶來了官方的多線程支持——std::thread,從此不用再依賴操作系統特定的API或第三方庫,寫多線程程序方便多了!

第一步:創建你的第一個線程

好,閑話少說,直接上代碼看看怎么創建一個線程:

#include <iostream>
#include <thread>

// 這是我們要在新線程中執行的函數
void hello_thread() {
    std::cout << "哈嘍,我是一個新線程!" << std::endl;
}

int main() {
    // 創建一個執行hello_thread函數的線程
    std::thread t(hello_thread);

    // 主線程打個招呼
    std::cout << "主線程:我正在等一個線程干活..." << std::endl;

    // 等待線程完成
    t.join();

    std::cout << "所有線程都結束了,程序退出!" << std::endl;
    return 0;
}

輸出結果可能是:

主線程:我正在等一個線程干活...
哈嘍,我是一個新線程!
所有線程都結束了,程序退出!

或者是:

哈嘍,我是一個新線程!
主線程:我正在等一個線程干活...
所有線程都結束了,程序退出!

咦?為啥輸出順序不固定?因為兩個線程是并發執行的,誰先打印完全看 CPU 的心情!這就是多線程的特點——不確定性。

代碼解析:

  • 創建線程超簡單,就一行代碼:std::thread t(hello_thread);。線程一創建就立刻開始執行了。
  • t.join() 是啥意思呢?它相當于說:"主線程,你等等這個新線程,等它干完活再繼續"。如果沒有這行,主線程可能提前結束,程序就崩潰了!

給線程傳參數

線程不能只會喊"哈嘍"吧?我們得給它點實際任務,還得告訴它一些參數。傳參數超簡單:

#include <iostream>
#include <thread>
#include <string>

void greeting(std::string name, int times) {
    for (int i = 0; i < times; i++) {
        std::cout << "你好," << name << "!這是第 " << (i+1) << " 次問候!" << std::endl;
    }
}

int main() {
    // 創建線程并傳遞參數
    std::thread t(greeting, "張三", 3);

    std::cout << "主線程:我讓線程去問候張三了..." << std::endl;

    // 等待線程完成
    t.join();

    std::cout << "問候完畢!" << std::endl;
    return 0;
}

輸出結果:

主線程:我讓線程去問候張三了...
你好,張三!這是第 1 次問候!
你好,張三!這是第 2 次問候!
你好,張三!這是第 3 次問候!
問候完畢!

傳參就像普通函數調用一樣,直接在線程構造函數后面加參數就行。但是有個坑:參數是"拷貝"到線程中的,所以小心對象的復制開銷!

用Lambda表達式創建線程

每次都要單獨寫個函數太麻煩了,有沒有簡單方法?有啊,用Lambda表達式!

#include <iostream>
#include <thread>

int main() {
    // 使用Lambda表達式創建線程
    std::thread t([]() {
        std::cout << "我是Lambda創建的線程,帥不帥?" << std::endl;
        for (int i = 5; i > 0; i--) {
            std::cout << "倒計時: " << i << std::endl;
        }
    });

    std::cout << "主線程:Lambda線程正在倒計時..." << std::endl;

    t.join();

    std::cout << "倒計時結束!" << std::endl;
    return 0;
}

Lambda表達式就像一個臨時小函數,用完就扔,方便得很!特別適合那種只用一次的簡單邏輯。

多線程通信的坑:數據競爭

多線程編程最大的坑就是多個線程同時訪問同一數據時會出現"數據競爭"。來看個例子:

#include <iostream>
#include <thread>
#include <vector>

int counter = 0; // 共享的計數器

void increment_counter(int times) {
    for (int i = 0; i < times; i++) {
        counter++; // 危險操作!多線程同時修改
    }
}

int main() {
    std::vector<std::thread> threads;

    // 創建5個線程,每個線程將counter增加10000次
    for (int i = 0; i < 5; i++) {
        threads.push_back(std::thread(increment_counter, 10000));
    }

    // 等待所有線程完成
    for (auto& t : threads) {
        t.join();
    }

    std::cout << "理論上counter應該等于:" << 5 * 10000 << std::endl;
    std::cout << "實際上counter等于:" << counter << std::endl;

    return 0;
}

輸出可能是:

理論上counter應該等于:50000
實際上counter等于:42568

咦?怎么少了那么多?因為 counter++ 看起來是一條語句,但實際上分三步:讀取counter的值、加1、寫回counter。當多個線程同時執行這個操作,就會互相"踩踏",導致最終結果小于預期。

這就是臭名昭著的數據競爭問題,解決方法有互斥鎖、原子操作等,后面會講。

線程管理的基本操作

(1) join() - 等待線程完成

我們已經見過 join() 了,它會阻塞當前線程,直到目標線程執行完畢。

std::thread t(some_function);
t.join(); // 等待t完成

(2) detach() - 讓線程"自生自滅"

有時候,我們啟動一個線程后不想等它了,可以用 detach() 讓它獨立運行:

std::thread t(background_task);
t.detach(); // 線程在后臺獨立運行
std::cout << "主線程不管子線程了,繼續自己的事" << std::endl;

detach后的線程稱為"分離線程"或"守護線程",它會在后臺默默運行,直到自己的任務完成。但要小心:如果主程序結束了,這些分離線程會被強制終止!

(3) joinable() - 檢查線程是否可等待

在join之前,最好檢查一下線程是否可以被等待:

std::thread t(some_function);
// ... 一些代碼 ...
if (t.joinable()) {
    t.join();
}

這避免了對已經 join 或 detach 過的線程再次操作,否則會崩潰。

防止忘記join:RAII風格的線程包裝器

C++的經典模式:用對象的生命周期管理資源。我們可以創建一個線程包裝器,在析構時自動join:

#include <iostream>
#include <thread>

class thread_guard {
private:
std::thread& t;

public:
// 構造函數,接收線程引用
explicit thread_guard(std::thread& t_) : t(t_) {}

// 析構函數,自動join線程
~thread_guard() {
    if (t.joinable()) {
        t.join();
    }
}

// 禁止復制
thread_guard(const thread_guard&) = delete;
thread_guard& operator=(const thread_guard&) = delete;
};

void some_function() {
    std::cout << "線程工作中..." << std::endl;
}

int main() {
    std::thread t(some_function);
    thread_guard g(t); // 創建守衛對象

    // 即使這里拋出異常,thread_guard的析構函數也會被調用,確保t被join
    std::cout << "主線程繼續工作..." << std::endl;

    return 0; // 函數結束,g被銷毀,自動調用t.join()
}

這樣即使發生異常,或者開發者忘記手動join,線程也會被正確等待,避免程序崩潰。

線程間的互斥:mutex

前面說到數據競爭問題,最常用的解決方案是互斥鎖(mutex):

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

int counter = 0;
std::mutex counter_mutex; // 保護counter的互斥鎖

void safe_increment(int times) {
    for (int i = 0; i < times; i++) {
        counter_mutex.lock(); // 鎖定互斥鎖
        counter++; // 安全操作
        counter_mutex.unlock(); // 解鎖
    }
}

int main() {
    std::vector<std::thread> threads;

    // 創建5個線程
    for (int i = 0; i < 5; i++) {
        threads.push_back(std::thread(safe_increment, 10000));
    }

    // 等待所有線程
    for (auto& t : threads) {
        t.join();
    }

    std::cout << "現在counter正確等于:" << counter << std::endl;
    return 0;
}

輸出:

現在counter正確等于:50000

太好了!結果正確了。但這樣手動lock/unlock很容易出錯,如果忘記unlock或者發生異常,就會死鎖。所以更推薦使用RAII風格的std::lock_guard:

void better_safe_increment(int times) {
    for (int i = 0; i < times; i++) {
        std::lock_guard<std::mutex> lock(counter_mutex); // 自動鎖定和解鎖
        counter++;
    }
}

lock_guard在構造時鎖定互斥鎖,在析構時自動解鎖,無論是正常退出還是異常退出都能保證互斥鎖被釋放。

高級話題:條件變量

線程間的同步不只有互斥,有時我們需要一個線程等待某個條件滿足。條件變量就是干這個的:

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

std::queue<int> data_queue; // 共享的數據隊列
std::mutex queue_mutex;
std::condition_variable data_cond;

// 生產者線程
void producer() {
    for (int i = 0; i < 5; i++) {
        {
            std::lock_guard<std::mutex> lock(queue_mutex);
            data_queue.push(i); // 添加數據
            std::cout << "生產了數據: " << i << std::endl;
        } // 鎖在這里釋放

        data_cond.notify_one(); // 通知一個等待的消費者
        std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 稍微等一下
    }
}

// 消費者線程
void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(queue_mutex);
        // 等待隊列有數據(避免虛假喚醒)
        data_cond.wait(lock, [] { return !data_queue.empty(); });

        // 取出并處理數據
        int value = data_queue.front();
        data_queue.pop();

        std::cout << "消費了數據: " << value << std::endl;

        if (value == 4) break; // 收到最后一個數據后退出
    }
}

int main() {
    std::thread prod(producer);
    std::thread cons(consumer);

    prod.join();
    cons.join();

    std::cout << "所有數據都生產和消費完畢!" << std::endl;
    return 0;
}

這個例子展示了經典的"生產者-消費者"模式:生產者往隊列里放數據,消費者從隊列里取數據。條件變量確保消費者不會在隊列為空時嘗試取數據。

線程與異常安全

在多線程程序中處理異常尤為重要。如果線程執行時拋出異常,且沒被捕獲,整個程序會直接崩潰!以下是安全處理方式:

#include <iostream>
#include <thread>
#include <exception>

void function_that_throws() {
    throw std::runtime_error("故意拋出的異常!");
}

void thread_function() {
    try {
        function_that_throws();
    } catch (const std::exception& e) {
        std::cout << "線程捕獲到異常: " << e.what() << std::endl;
    }
}

int main() {
    std::thread t(thread_function);
    t.join();

    std::cout << "程序正常結束" << std::endl;
    return 0;
}

輸出:

線程捕獲到異常: 故意拋出的異常!
程序正常結束

記住:每個線程都有自己獨立的調用棧,異常不會跨線程傳播!在哪個線程拋出,就必須在哪個線程捕獲。

實用技巧

(1) 獲取線程ID

每個線程都有唯一的ID,用于標識:

#include <iostream>
#include <thread>

void print_id() {
    std::cout << "線程ID: " << std::this_thread::get_id() << std::endl;
}

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

    std::cout << "主線程ID: " << std::this_thread::get_id() << std::endl;
    std::cout << "t1的ID: " << t1.get_id() << std::endl;
    std::cout << "t2的ID: " << t2.get_id() << std::endl;

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

    return 0;
}

(2) 線程休眠

有時需要讓線程暫停一會兒:

#include <iostream>
#include <thread>
#include <chrono>

void sleepy_thread() {
    std::cout << "我要睡覺了..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::cout << "睡醒了!" << std::endl;
}

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

(3) 獲取CPU核心數

為了根據CPU核心優化線程數量:

#include <iostream>
#include <thread>

int main() {
    unsigned int num_cores = std::thread::hardware_concurrency();
    std::cout << "你的CPU有 " << num_cores << " 個硬件線程(核心)" << std::endl;

    // 根據核心數創建線程
    unsigned int num_threads = num_cores;
    std::cout << "將創建 " << num_threads << " 個線程以充分利用CPU" << std::endl;

    return 0;
}

實際案例:并行圖像處理

來個實際應用案例:用多線程加速圖像處理。這里我們簡化為操作一個二維數組:

#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>
#include <chrono>

// 模擬圖像處理函數
void process_image_part(std::vector<std::vector<int>>& image, int start_row, int end_row) {
    for (int i = start_row; i < end_row; i++) {
        for (int j = 0; j < image[i].size(); j++) {
            // 模擬復雜處理,例如圖像模糊
            image[i][j] = (image[i][j] + 10) * 2;
            // 模擬耗時操作
            std::this_thread::sleep_for(std::chrono::microseconds(1));
        }
    }
}

int main() {
    // 創建模擬圖像 (1000x1000)
    std::vector<std::vector<int>> image(1000, std::vector<int>(1000, 5));

    // 獲取CPU核心數
    unsigned int num_cores = std::thread::hardware_concurrency();
    unsigned int num_threads = num_cores; // 使用和核心數一樣多的線程

    std::cout << "使用 " << num_threads << " 個線程處理圖像..." << std::endl;

    // 開始計時
    auto start_time = std::chrono::high_resolution_clock::now();

    // 創建線程并分配工作
    std::vector<std::thread> threads;
    int rows_per_thread = image.size() / num_threads;

    for (unsigned int i = 0; i < num_threads; i++) {
        int start_row = i * rows_per_thread;
        int end_row = (i == num_threads - 1) ? image.size() : (i + 1) * rows_per_thread;

        threads.push_back(std::thread(process_image_part, std::ref(image), start_row, end_row));
    }

    // 等待所有線程完成
    for (auto& t : threads) {
        t.join();
    }

    // 結束計時
    auto end_time = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);

    std::cout << "圖像處理完成!耗時: " << duration.count() << " 毫秒" << std::endl;

    // 驗證結果(只顯示部分)
    std::cout << "處理后的圖像樣本(左上角): " << std::endl;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            std::cout << image[i][j] << " ";
        }
        std::cout << std::endl;
    }

    return 0;
}

這個例子展示了如何將大型任務分解成多個小塊,分配給多個線程并行處理,充分利用多核CPU的優勢。

多線程的優秀實踐

  • 保持簡單:多線程代碼難以調試,盡量簡化每個線程的工作。
  • 避免共享狀態:盡可能減少線程間共享的數據,以降低同步復雜度。
  • 適當的線程數量:通常等于或略多于CPU核心數,太多反而會因為頻繁切換導致性能下降。
  • 使用高級抽象:考慮使用std::async、std::future或線程池,而不是直接管理線程。
  • 測試和調試:在各種條件下測試多線程代碼,包括高負載和邊緣情況。

結語

從此,你已經掌握了C++11多線程編程的基礎知識!從創建線程到傳遞參數,從互斥鎖到條件變量,從簡單示例到實際應用。多線程編程確實比單線程復雜,但掌握了這些技能,你就能寫出更高效、響應更快的程序。

記住,多線程編程需要實踐和耐心。開始時可能會遇到各種莫名其妙的問題,但隨著經驗積累,你會越來越熟練。不妨從簡單的多線程程序開始,逐步挑戰更復雜的場景。

最后的建議:寫多線程程序時,時刻保持清醒和警惕,因為多線程bug可能是最難調試的bug之一!

愿你的多線程之旅愉快且充滿成就感!

責任編輯:趙寧寧 來源: 跟著小康學編程
相關推薦

2024-06-12 12:28:23

2025-03-28 03:20:00

MySQL數據庫搜索

2020-09-29 07:54:05

Express 飛起

2011-04-13 10:51:58

MATLAB

2025-04-15 00:00:00

2024-11-25 18:00:00

C#代碼編程

2019-11-05 10:35:57

SpringBoot調優Java

2023-03-01 23:59:23

Java開發

2021-07-13 07:52:03

SQL面試COUNT(*)

2011-09-27 13:25:05

Web

2025-04-22 03:00:00

2021-01-04 15:11:57

開發 IDEA代碼

2024-11-27 09:46:34

2013-01-07 09:34:43

CodeLoveBAT

2011-02-25 08:39:11

QFabric數據中心Juniper

2025-01-17 09:23:31

2024-11-14 00:06:56

2025-05-22 08:04:43

2016-05-11 09:18:21

AWS云數據倉庫Redshift

2019-03-25 08:05:35

Elasticsear優化集群
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产精品成人一区二区三区夜夜夜 | 一区二区三区免费 | 国产片侵犯亲女视频播放 | 国产精品久久久久久久久久免费看 | 日韩欧美黄色 | 99久久婷婷国产综合精品电影 | 免费精品视频一区 | 久久久久一区二区三区 | 欧美日韩手机在线观看 | 免费黄色在线观看 | 欧美色偷拍 | 日本一区二区电影 | 成人免费黄视频 | 日韩欧美三区 | 看特级黄色片 | 欧美成人a∨高清免费观看 色999日韩 | 91精品国产综合久久婷婷香蕉 | 黄色片免费在线观看 | 精品免费国产视频 | 天天躁天天操 | 日韩av在线一区 | 精品一区二区电影 | 国内精品一区二区三区 | 亚洲大片在线观看 | 91aiai| 一区二区在线不卡 | 不卡一区二区在线观看 | 中文字幕国产 | 久久久激情| 日本久久综合 | 在线日韩视频 | 国产欧美日韩一区二区三区在线 | 亚洲天堂一区二区 | 国产69精品久久99不卡免费版 | 亚洲精品中文字幕 | 国产999在线观看 | 亚洲精品女优 | 久久久久久国产精品免费免费男同 | 午夜a√ | 欧美精品一二三区 | 亚洲一区二区av |