C++標準線程庫之入門

自c++11版本後,標準庫也提供了對線程的支持。雖然大多場合還是使用其他的三方線程庫,如:boost::thread, QThread等,但是學習下還是有必要的。

1. std::thread簡介

std::thread類即創建子線程的類,定義於頭文件thread中。

std::thread類僅不到十個公開成員函數,同時無法進行拷貝,只能使用移動構造和賦值轉移所有權。

2. CPU支持的線程併發數

#include <thread>
[static]
unsigned int std::thread::hardware_concurrency();
// 返回硬件支持的線程數目,失敗返回0
#include <iostream>
#include <thread>

int main()
{
    std::cout << "CPU是" << std::thread::hardware_concurrency() << "線程" << std::endl;
    return 0;
}

3. 創建子線程

#include <thread>
[public]
template<typename _Callable, typename... _Args>
std::thread::thread(_Callable&& __f, _Args&&... __args);
// 構造函數之一, 傳一個可以調用的函數或者對象作爲任務, 有子線程來處理運行這個任務。
// 未調用此構造函數的線程是無法執行的,稱爲空任務線程。
// 需要明確的是構造完成後,子線程即開始執行,而不是等join(結合)或者detach(分離)調用才執行

bool std::thread::joinable() const;
// 未被結合或者分離的非空任務線程會返回true, 否則返回false。

std::thread::id std::thread::get_id() const;
// 未被結合或者分離的非空任務線程會返回非0的整數作爲子線程的標識符
#include <iostream>
#include <thread>

void func(int a)
{
    std::cout << "This function is callable: " << a << std::endl;
}

class callable_obj
{
public:
    // 對象可以有類似函數的調用
    void operator ()(int a)
    {
        std::cout << "This object is callable: " << a << std::endl;
    }
};

int main()
{
    // 可調用的函數初始化線程
    std::thread t1(func, 1);
    // // t1.get_id(): 很大的數  t1.joinable(): true
    std::cout << "t1 thread_id: " << t1.get_id() << " joinable: " << t1.joinable() << std::endl;

    
    // 可調用的對象初始化線程
    std::thread t2;
    // t2.get_id(): 0  t2.joinable(): false
    std::cout << "t2 thread_id: " << t2.get_id() << " joinable: " << t2.joinable() << std::endl;
    callable_obj obj;
    // 移動賦值, 也可以用t1的直接構造
    t2 = std::move(std::thread(obj, 2));
    // t2.get_id(): 很大的數  t2.joinable(): true
    std::cout << "t2 thread_id: " << t2.get_id() << " joinable: " << t2.joinable() << std::endl;

    return 0;
}
t1 thread_id: 140549800912640 joinable: This function is callable: 11
t2 thread_id: thread::id of a non-executing thread joinable: 0

t2 thread_id: 140549792519936 joinable: 1
terminate called without an active exception
This object is callable: 2

需要主要的我們並沒有顯示執行t1和t2線程,但是線程還是執行了,但是會發生異常。

4. 子線程等待方式

#include <thread>
[public]
void std::thread::join();
// 僅當joinable爲true才能調用本函數, 否則拋出異常system_error。
// 本函數是使主線程進入阻塞狀態,等待子線程執行完成後,主線程在繼續執行,joinable函數返回false。
// 當子線程執行結束後,資源由主線程回收,主線程繼續執行後續指令。

void std::thread::detach();
// 僅當joinable爲true才能調用本函數, 否則拋出異常system_error。
// 本函數調用後主線程不會去等待子線程是否執行完成,主線程繼續執行後續指令,joinable函數返回false。
// 可能會發生主線程已經執行結束,子線程還未運行結束的情況。
// 當線程結束時,分離的線程由系統回收。
#include <iostream>
#include <thread>

void func(int a)
{
    std::cout << "This function is callable: " << a << std::endl;
}

class callable_obj
{
public:
    void operator ()(int a)
    {
        std::cout << "This object is callable: " << a << std::endl;
    }
};

int main()
{
    std::thread t1(func, 1);
    // 分離,後面的代碼繼續處理
    t1.detach();

    callable_obj obj;
    std::thread t2(std::thread(obj, 2));
    // 線程進入阻塞, t2線程執行完成纔會執行return 0
    t2.join();
	
    return 0;
}
This function is callable: 1
This object is callable: 2

本實例並沒有什麼不對, 都會輸出,因爲主線程處理後續代碼的時間較長, t1大概率會在主線程執行完執行結束。因此,不會發生什麼特殊的情況,但是很多時候分離需要考慮到這個問題。

5. 傳參問題

傳參問題還需要多注意下,會出現一些問題。在參數是值的情況正常不會發生,但是如果參數有引用就會發生錯誤,需要使用std::ref來告訴其傳遞的是引用 。

#include <iostream>
#include <thread>
#include <functional>

void func(int a, int& b)
{
    std::cout << "func: a = " << a << ", b = " << b << std::endl;
    b  = 7;
}

int main()
{
    int a = 2, b = 2;
    // std::thread t(func, a, b) 錯誤
    std::thread t(func, a, std::ref(b));
    t.join();
    std::cout << "main: a = " << a << ", b = " << b << std::endl;
    return 0;
}

func: a = 2, b = 2
main: a = 2, b = 7

6. 對線程類的薄封裝(RAll)

#include <iostream>
#include <thread>
#include <functional>

void func(int a, int& b)
{
    std::cout << "func: a = " << a << ", b = " << b << std::endl;
    b  = 7;
}

class thread_graud
{
    std::thread _t;
public:
    thread_graud(std::thread& t) : _t(std::move(t)) {}

    ~thread_graud()
    {
        if(_t.joinable()) _t.join();
    }

    thread_graud(const thread_graud&) = delete;
    thread_graud& operator =(const thread_graud&) = delete;
};

int main()
{
    int a = 2, b = 2;
    std::thread t(func, a, std::ref(b));
    thread_graud g(t);
    std::cout << "main: a = " << a << ", b = " << b << std::endl;
    return 0;
}

使用中間類thread_graud來隱式調用join方法。

7. 擴展

c++11的線程是可以使用平臺的特性擴展的,沒想到吧。

#include <thread>

std::native_handle_type std::native_handle();
// 本函數會返回對於平臺下底層句柄, 其條件也是joinable爲true, 本人的系統是linux,此函數就會返回pthread_t
#include <iostream>
#include <thread>
#include <pthread.h>

void func(int a)
{
    std::cout << "linux 系統調用成功 a = " << a << std::endl;
}

int main()
{
    int a = 2;
    std::thread t(func, a);
    // 使用linux平臺下的pthread_join來調用線程
    pthread_join(t.native_handle(), nullptr);
    return 0;
}

linux 系統調用成功 a = 2
terminate called without an active exception

雖然可以這樣來用, 但是由於使用linux下的線程函數啓動了線程,而joinable的值並沒有發生改變,線程對象析構時還是會調用std::terminate(),導致了異常的發生。

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