C++11多線程(簡約但不簡單)

一、簡單使用

C++11提供了一套精練的線程庫,小巧且易用。運行一個線程,可以直接創(chuàng)建一個std::thread的實例,線程在實例成功構(gòu)造成時啟動。若有底層平臺支持,成員函數(shù)std::thread::native_handle()將可提供對原生線程對象運行平臺特定的操作。

#include <thread>
#include <iostream>

void foo() {
    std::cout << "Hello C++11" << std::endl;
}

int main() {
    std::thread thread(foo);  // 啟動線程foo
    thread.join();  // 等待線程執(zhí)行完成

    return 0;
}

編譯并運行,程序輸出:

Hello C++11

1、線程參數(shù)

當(dāng)需要向線程傳遞參數(shù)時,可以直接通過std::thread的構(gòu)造函數(shù)參數(shù)進行,構(gòu)造函數(shù)通過完美轉(zhuǎn)發(fā)將參數(shù)傳遞給線程函數(shù)。

#include <thread>
#include <iostream>

void hello(const char *name) {
    std::cout << "Hello " << name << std::endl;
}

int main() {
    std::thread thread(hello, "C++11");
    thread.join();

    return 0;
}

2. 類成員函數(shù)做為線程入口

類成員函數(shù)做為線程入口時,仍然十分簡單: 把this做為第一個參數(shù)傳遞進去即可。

#include <thread>
#include <iostream>

class Greet
{
    const char *owner = "Greet";
public:
    void SayHello(const char *name) {
        std::cout << "Hello " << name << " from " << this->owner << std::endl;
    }
};
int main() {
    Greet greet;

    std::thread thread(&Greet::SayHello, &greet, "C++11");
    thread.join();

    return 0;
}
//輸出:Hello C++11 from Greet

3. join: 等待線程執(zhí)行完成

線程如果像二哈似的撒手沒,則程序鐵定悲劇。因此std::thread提供了幾個線程管理的工具,其中join就是很重要的一個:等待線程執(zhí)行完成。即使當(dāng)線程函數(shù)已經(jīng)執(zhí)行完成后,調(diào)用join仍然是有效的。

4. 線程暫停

從外部讓線程暫停,會引發(fā)很多并發(fā)問題。大家可以百度一下,此處不做引申。這大概也是std::thread并沒有直接提供pause函數(shù)的原因。但有時線程在運行時,確實需要“停頓”一段時間怎么辦呢?可以使用std::this_thread::sleep_for或std::this_thread::sleep_until

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

using namespace std::chrono;

void pausable() {
    // sleep 500毫秒
    std::this_thread::sleep_for(milliseconds(500));
    // sleep 到指定時間點
    std::this_thread::sleep_until(system_clock::now() + milliseconds(500));
}

int main() {
    std::thread thread(pausable);
    thread.join();

    return 0;
}

5. 線程停止

一般情況下當(dāng)線程函數(shù)執(zhí)行完成后,線程“自然”停止。但在std::thread中有一種情況會造成線程異常終止,那就是:析構(gòu)。當(dāng)std::thread實例析構(gòu)時,如果線程還在運行,則線程會被強行終止掉,這可能會造成資源的泄漏,因此盡量在析構(gòu)前join一下,以確保線程成功結(jié)束。
如果確實想提前讓線程結(jié)束怎么辦呢?一個簡單的方法是使用“共享變量”,線程定期地去檢測該量,如果需要退出,則停止執(zhí)行,退出線程函數(shù)。使用“共享變量”需要注意,在多核、多CPU的情況下需要使用“原子”操作,關(guān)于原子操作后面會有專題講述。

二、進階(更多你可能需要知道的)

1. 拷貝

std::thread a(foo);
std::thread b;
b = a;

當(dāng)執(zhí)行以上代碼時,會發(fā)生什么?最終foo線程是由a管理,還是b來管理?答案是由b來管理。std::thread被設(shè)計為只能由一個實例來維護線程狀態(tài),以及對線程進行操作。因此當(dāng)發(fā)生賦值操作時,會發(fā)生線程所有權(quán)轉(zhuǎn)移。在macos下std::thread的賦值函數(shù)原型為:

thread& operator=(thread&& a);

賦完值后,原來由a管理的線程改為由b管理,b不再指向任何線程(相當(dāng)于執(zhí)行了detach操作)。如果b原本指向了一個線程,那么這個線程會被終止掉。

2. detach/joinable

detach是std::thread的成員函數(shù),函數(shù)原型為:

void detach();
bool joinable() const;

detach以后就失去了對線程的所有權(quán),不能再調(diào)用join了,因為線程已經(jīng)分離出去了,不再歸該實例管了。判斷線程是否還有對線程的所有權(quán)的一個簡單方式是調(diào)用joinable函數(shù),返回true則有,否則為無。

3. 線程內(nèi)部調(diào)用自身的join

自己等待自己執(zhí)行結(jié)束?如果程序員真這么干,那這個程序員一定是腦子短路了。對于這種行為C++11只能拋異常了。

三、其它

1. get_id

每個線程都有一個id,但此處的get_id與系統(tǒng)分配給線程的ID并不一是同一個東東。如果想取得系統(tǒng)分配的線程ID,可以調(diào)用native_handle函數(shù)。

2. 邏輯運算?

有些平臺下std::thread還支持若干邏輯運算,比如Visual C++, 但這并不是標(biāo)準(zhǔn)庫的行為,不要在跨平臺的場景中使用。

目錄 下一篇
C++11多線程-mutex
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容