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

一、簡單使用

C++11提供了一套精練的線程庫,小巧且易用。運行一個線程,可以直接創建一個std::thread的實例,線程在實例成功構造成時啓動。若有底層平臺支持,成員函數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();  // 等待線程執行完成

    return 0;
}

編譯並運行,程序輸出:

Hello C++11

1、線程參數

當需要向線程傳遞參數時,可以直接通過std::thread的構造函數參數進行,構造函數通過完美轉發將參數傳遞給線程函數。

#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. 類成員函數做爲線程入口

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

#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: 等待線程執行完成

線程如果像二哈似的撒手沒,則程序鐵定悲劇。因此std::thread提供了幾個線程管理的工具,其中join就是很重要的一個:等待線程執行完成。即使當線程函數已經執行完成後,調用join仍然是有效的。

4. 線程暫停

從外部讓線程暫停,會引發很多併發問題。大家可以百度一下,此處不做引申。這大概也是std::thread並沒有直接提供pause函數的原因。但有時線程在運行時,確實需要“停頓”一段時間怎麼辦呢?可以使用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. 線程停止

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

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

1. 拷貝

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

當執行以上代碼時,會發生什麼?最終foo線程是由a管理,還是b來管理?答案是由b來管理。std::thread被設計爲只能由一個實例來維護線程狀態,以及對線程進行操作。因此當發生賦值操作時,會發生線程所有權轉移。在macos下std::thread的賦值函數原型爲:

thread& operator=(thread&& a);

賦完值後,原來由a管理的線程改爲由b管理,b不再指向任何線程(相當於執行了detach操作)。如果b原本指向了一個線程,那麼這個線程會被終止掉。

2. detach/joinable

detach是std::thread的成員函數,函數原型爲:

void detach();
bool joinable() const;

detach以後就失去了對線程的所有權,不能再調用join了,因爲線程已經分離出去了,不再歸該實例管了。判斷線程是否還有對線程的所有權的一個簡單方式是調用joinable函數,返回true則有,否則爲無。

3. 線程內部調用自身的join

自己等待自己執行結束?如果程序員真這麼幹,那這個程序員一定是腦子短路了。對於這種行爲C++11只能拋異常了。

三、其它

1. get_id

每個線程都有一個id,但此處的get_id與系統分配給線程的ID並不一是同一個東東。如果想取得系統分配的線程ID,可以調用native_handle函數。

2. 邏輯運算?

有些平臺下std::thread還支持若干邏輯運算,比如Visual C++, 但這並不是標準庫的行爲,不要在跨平臺的場景中使用。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章