玩轉(zhuǎn) C++11 多線程:讓你的程序飛起來的 std::thread 終極指南
前言:為啥要學(xué)多線程?
想象一下,你正在廚房做飯。如果你是單線程工作,那就只能先切菜,切完再炒菜,炒完再煮湯...一項一項按順序來。但現(xiàn)實中的你肯定是多線程操作啊:鍋里炒著菜,同時旁邊的電飯煲在煮飯,熱水壺在燒水,也許你還能同時看看手機(jī)...這就是多線程的威力!
在程序世界里,多線程就像多了幾個"分身",可以同時處理不同的任務(wù),充分利用多核CPU的性能,讓程序跑得飛快。特別是現(xiàn)在誰的電腦不是多核啊,不用多線程簡直是浪費資源!

C++11標(biāo)準(zhǔn)終于給我們帶來了官方的多線程支持——std::thread,從此不用再依賴操作系統(tǒng)特定的API或第三方庫,寫多線程程序方便多了!
第一步:創(chuàng)建你的第一個線程
好,閑話少說,直接上代碼看看怎么創(chuàng)建一個線程:
#include <iostream>
#include <thread>
// 這是我們要在新線程中執(zhí)行的函數(shù)
void hello_thread() {
    std::cout << "哈嘍,我是一個新線程!" << std::endl;
}
int main() {
    // 創(chuàng)建一個執(zhí)行hello_thread函數(shù)的線程
    std::thread t(hello_thread);
    // 主線程打個招呼
    std::cout << "主線程:我正在等一個線程干活..." << std::endl;
    // 等待線程完成
    t.join();
    std::cout << "所有線程都結(jié)束了,程序退出!" << std::endl;
    return 0;
}輸出結(jié)果可能是:
主線程:我正在等一個線程干活...
哈嘍,我是一個新線程!
所有線程都結(jié)束了,程序退出!或者是:
哈嘍,我是一個新線程!
主線程:我正在等一個線程干活...
所有線程都結(jié)束了,程序退出!咦?為啥輸出順序不固定?因為兩個線程是并發(fā)執(zhí)行的,誰先打印完全看 CPU 的心情!這就是多線程的特點——不確定性。
代碼解析:
- 創(chuàng)建線程超簡單,就一行代碼:std::thread t(hello_thread);。線程一創(chuàng)建就立刻開始執(zhí)行了。
 - t.join() 是啥意思呢?它相當(dāng)于說:"主線程,你等等這個新線程,等它干完活再繼續(xù)"。如果沒有這行,主線程可能提前結(jié)束,程序就崩潰了!
 
給線程傳參數(shù)
線程不能只會喊"哈嘍"吧?我們得給它點實際任務(wù),還得告訴它一些參數(shù)。傳參數(shù)超簡單:
#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() {
    // 創(chuàng)建線程并傳遞參數(shù)
    std::thread t(greeting, "張三", 3);
    std::cout << "主線程:我讓線程去問候張三了..." << std::endl;
    // 等待線程完成
    t.join();
    std::cout << "問候完畢!" << std::endl;
    return 0;
}輸出結(jié)果:
主線程:我讓線程去問候張三了...
你好,張三!這是第 1 次問候!
你好,張三!這是第 2 次問候!
你好,張三!這是第 3 次問候!
問候完畢!傳參就像普通函數(shù)調(diào)用一樣,直接在線程構(gòu)造函數(shù)后面加參數(shù)就行。但是有個坑:參數(shù)是"拷貝"到線程中的,所以小心對象的復(fù)制開銷!
用Lambda表達(dá)式創(chuàng)建線程
每次都要單獨寫個函數(shù)太麻煩了,有沒有簡單方法?有啊,用Lambda表達(dá)式!
#include <iostream>
#include <thread>
int main() {
    // 使用Lambda表達(dá)式創(chuàng)建線程
    std::thread t([]() {
        std::cout << "我是Lambda創(chuàng)建的線程,帥不帥?" << std::endl;
        for (int i = 5; i > 0; i--) {
            std::cout << "倒計時: " << i << std::endl;
        }
    });
    std::cout << "主線程:Lambda線程正在倒計時..." << std::endl;
    t.join();
    std::cout << "倒計時結(jié)束!" << std::endl;
    return 0;
}Lambda表達(dá)式就像一個臨時小函數(shù),用完就扔,方便得很!特別適合那種只用一次的簡單邏輯。
多線程通信的坑:數(shù)據(jù)競爭
多線程編程最大的坑就是多個線程同時訪問同一數(shù)據(jù)時會出現(xiàn)"數(shù)據(jù)競爭"。來看個例子:
#include <iostream>
#include <thread>
#include <vector>
int counter = 0; // 共享的計數(shù)器
void increment_counter(int times) {
    for (int i = 0; i < times; i++) {
        counter++; // 危險操作!多線程同時修改
    }
}
int main() {
    std::vector<std::thread> threads;
    // 創(chuàng)建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應(yīng)該等于:" << 5 * 10000 << std::endl;
    std::cout << "實際上counter等于:" << counter << std::endl;
    return 0;
}輸出可能是:
理論上counter應(yīng)該等于:50000
實際上counter等于:42568咦?怎么少了那么多?因為 counter++ 看起來是一條語句,但實際上分三步:讀取counter的值、加1、寫回counter。當(dāng)多個線程同時執(zhí)行這個操作,就會互相"踩踏",導(dǎo)致最終結(jié)果小于預(yù)期。
這就是臭名昭著的數(shù)據(jù)競爭問題,解決方法有互斥鎖、原子操作等,后面會講。
線程管理的基本操作
(1) join() - 等待線程完成
我們已經(jīng)見過 join() 了,它會阻塞當(dāng)前線程,直到目標(biāo)線程執(zhí)行完畢。
std::thread t(some_function);
t.join(); // 等待t完成(2) detach() - 讓線程"自生自滅"
有時候,我們啟動一個線程后不想等它了,可以用 detach() 讓它獨立運(yùn)行:
std::thread t(background_task);
t.detach(); // 線程在后臺獨立運(yùn)行
std::cout << "主線程不管子線程了,繼續(xù)自己的事" << std::endl;detach后的線程稱為"分離線程"或"守護(hù)線程",它會在后臺默默運(yùn)行,直到自己的任務(wù)完成。但要小心:如果主程序結(jié)束了,這些分離線程會被強(qiáng)制終止!
(3) joinable() - 檢查線程是否可等待
在join之前,最好檢查一下線程是否可以被等待:
std::thread t(some_function);
// ... 一些代碼 ...
if (t.joinable()) {
    t.join();
}這避免了對已經(jīng) join 或 detach 過的線程再次操作,否則會崩潰。
防止忘記join:RAII風(fēng)格的線程包裝器
C++的經(jīng)典模式:用對象的生命周期管理資源。我們可以創(chuàng)建一個線程包裝器,在析構(gòu)時自動join:
#include <iostream>
#include <thread>
class thread_guard {
private:
std::thread& t;
public:
// 構(gòu)造函數(shù),接收線程引用
explicit thread_guard(std::thread& t_) : t(t_) {}
// 析構(gòu)函數(shù),自動join線程
~thread_guard() {
    if (t.joinable()) {
        t.join();
    }
}
// 禁止復(fù)制
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); // 創(chuàng)建守衛(wèi)對象
    // 即使這里拋出異常,thread_guard的析構(gòu)函數(shù)也會被調(diào)用,確保t被join
    std::cout << "主線程繼續(xù)工作..." << std::endl;
    return 0; // 函數(shù)結(jié)束,g被銷毀,自動調(diào)用t.join()
}這樣即使發(fā)生異常,或者開發(fā)者忘記手動join,線程也會被正確等待,避免程序崩潰。
線程間的互斥:mutex
前面說到數(shù)據(jù)競爭問題,最常用的解決方案是互斥鎖(mutex):
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
int counter = 0;
std::mutex counter_mutex; // 保護(hù)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;
    // 創(chuàng)建5個線程
    for (int i = 0; i < 5; i++) {
        threads.push_back(std::thread(safe_increment, 10000));
    }
    // 等待所有線程
    for (auto& t : threads) {
        t.join();
    }
    std::cout << "現(xiàn)在counter正確等于:" << counter << std::endl;
    return 0;
}輸出:
現(xiàn)在counter正確等于:50000太好了!結(jié)果正確了。但這樣手動lock/unlock很容易出錯,如果忘記unlock或者發(fā)生異常,就會死鎖。所以更推薦使用RAII風(fēng)格的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在構(gòu)造時鎖定互斥鎖,在析構(gòu)時自動解鎖,無論是正常退出還是異常退出都能保證互斥鎖被釋放。
高級話題:條件變量
線程間的同步不只有互斥,有時我們需要一個線程等待某個條件滿足。條件變量就是干這個的:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::queue<int> data_queue; // 共享的數(shù)據(jù)隊列
std::mutex queue_mutex;
std::condition_variable data_cond;
// 生產(chǎn)者線程
void producer() {
    for (int i = 0; i < 5; i++) {
        {
            std::lock_guard<std::mutex> lock(queue_mutex);
            data_queue.push(i); // 添加數(shù)據(jù)
            std::cout << "生產(chǎn)了數(shù)據(jù): " << 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);
        // 等待隊列有數(shù)據(jù)(避免虛假喚醒)
        data_cond.wait(lock, [] { return !data_queue.empty(); });
        // 取出并處理數(shù)據(jù)
        int value = data_queue.front();
        data_queue.pop();
        std::cout << "消費了數(shù)據(jù): " << value << std::endl;
        if (value == 4) break; // 收到最后一個數(shù)據(jù)后退出
    }
}
int main() {
    std::thread prod(producer);
    std::thread cons(consumer);
    prod.join();
    cons.join();
    std::cout << "所有數(shù)據(jù)都生產(chǎn)和消費完畢!" << std::endl;
    return 0;
}這個例子展示了經(jīng)典的"生產(chǎn)者-消費者"模式:生產(chǎn)者往隊列里放數(shù)據(jù),消費者從隊列里取數(shù)據(jù)。條件變量確保消費者不會在隊列為空時嘗試取數(shù)據(jù)。
線程與異常安全
在多線程程序中處理異常尤為重要。如果線程執(zhí)行時拋出異常,且沒被捕獲,整個程序會直接崩潰!以下是安全處理方式:
#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 << "程序正常結(jié)束" << std::endl;
    return 0;
}輸出:
線程捕獲到異常: 故意拋出的異常!
程序正常結(jié)束記?。好總€線程都有自己獨立的調(diào)用棧,異常不會跨線程傳播!在哪個線程拋出,就必須在哪個線程捕獲。
實用技巧
(1) 獲取線程ID
每個線程都有唯一的ID,用于標(biāo)識:
#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核心數(shù)
為了根據(jù)CPU核心優(yōu)化線程數(shù)量:
#include <iostream>
#include <thread>
int main() {
    unsigned int num_cores = std::thread::hardware_concurrency();
    std::cout << "你的CPU有 " << num_cores << " 個硬件線程(核心)" << std::endl;
    // 根據(jù)核心數(shù)創(chuàng)建線程
    unsigned int num_threads = num_cores;
    std::cout << "將創(chuàng)建 " << num_threads << " 個線程以充分利用CPU" << std::endl;
    return 0;
}實際案例:并行圖像處理
來個實際應(yīng)用案例:用多線程加速圖像處理。這里我們簡化為操作一個二維數(shù)組:
#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>
#include <chrono>
// 模擬圖像處理函數(shù)
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++) {
            // 模擬復(fù)雜處理,例如圖像模糊
            image[i][j] = (image[i][j] + 10) * 2;
            // 模擬耗時操作
            std::this_thread::sleep_for(std::chrono::microseconds(1));
        }
    }
}
int main() {
    // 創(chuàng)建模擬圖像 (1000x1000)
    std::vector<std::vector<int>> image(1000, std::vector<int>(1000, 5));
    // 獲取CPU核心數(shù)
    unsigned int num_cores = std::thread::hardware_concurrency();
    unsigned int num_threads = num_cores; // 使用和核心數(shù)一樣多的線程
    std::cout << "使用 " << num_threads << " 個線程處理圖像..." << std::endl;
    // 開始計時
    auto start_time = std::chrono::high_resolution_clock::now();
    // 創(chuàng)建線程并分配工作
    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();
    }
    // 結(jié)束計時
    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;
    // 驗證結(jié)果(只顯示部分)
    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;
}這個例子展示了如何將大型任務(wù)分解成多個小塊,分配給多個線程并行處理,充分利用多核CPU的優(yōu)勢。
多線程的優(yōu)秀實踐
- 保持簡單:多線程代碼難以調(diào)試,盡量簡化每個線程的工作。
 - 避免共享狀態(tài):盡可能減少線程間共享的數(shù)據(jù),以降低同步復(fù)雜度。
 - 適當(dāng)?shù)木€程數(shù)量:通常等于或略多于CPU核心數(shù),太多反而會因為頻繁切換導(dǎo)致性能下降。
 - 使用高級抽象:考慮使用std::async、std::future或線程池,而不是直接管理線程。
 - 測試和調(diào)試:在各種條件下測試多線程代碼,包括高負(fù)載和邊緣情況。
 
結(jié)語
從此,你已經(jīng)掌握了C++11多線程編程的基礎(chǔ)知識!從創(chuàng)建線程到傳遞參數(shù),從互斥鎖到條件變量,從簡單示例到實際應(yīng)用。多線程編程確實比單線程復(fù)雜,但掌握了這些技能,你就能寫出更高效、響應(yīng)更快的程序。
記住,多線程編程需要實踐和耐心。開始時可能會遇到各種莫名其妙的問題,但隨著經(jīng)驗積累,你會越來越熟練。不妨從簡單的多線程程序開始,逐步挑戰(zhàn)更復(fù)雜的場景。
最后的建議:寫多線程程序時,時刻保持清醒和警惕,因為多線程bug可能是最難調(diào)試的bug之一!
愿你的多線程之旅愉快且充滿成就感!















 
 
 













 
 
 
 