參考:https://llfc.club/category?catid=225RaiVNI8pFDD5L4m807g7ZwmF#!aid/2Tuk4RfvfBC788LlqnQrWiPiEGW
1. 簡歷
- 本節介紹C++線程管控,包括移交線程的歸屬權,線程并發數量控制以及獲取線程id等基本操作。
2. 線程歸屬權
- 比如下面,我們說明了線程歸屬權的轉移方式
#include <iostream>
#include <thread>
#include <string>
void some_function()
{while (true){std::this_thread::sleep_for(std::chrono::seconds(1));}
}
void some_other_function()
{while (true){std::this_thread::sleep_for(std::chrono::seconds(1));}
}
int main()
{// t1 綁定some_functionstd::thread t1(some_function);// 2 轉移t1管理的線程給t2,轉移后t1無效std::thread t2 = std::move(t1);// 3 t1 可繼續綁定其他線程,執行some_other_functiont1 = std::thread(some_other_function);// 4 創建一個線程變量t3std::thread t3;// 5 轉移t2管理的線程給t3t3 = std::move(t2);// 6 轉移t3管理的線程給t1t1 = std::move(t3);std::this_thread::sleep_for(std::chrono::seconds(2000));return 0;
}
3. joining_thread
joinable()
bool joinable() const noexcept;
- 曾經有一份C++17標準的備選提案,主張引入新的類joining_thread,它與std::thread類似,但只要其執行析構函數,線程即能自動匯合,這點與scoped_thread非常像。可惜C++標準委員會未能達成共識,結果C++17標準沒有引入這個類,后來它改名為std::jthread,依然進入了C++20標準的議程(現已被正式納入C++20標準)。除去這些,實際上joining_thread類的代碼相對容易編寫
#include <iostream>
#include <thread>
#include <string>class joining_thread
{std::thread _t;public:joining_thread() noexcept = default;template <typename Callable, typename... Args>explicit joining_thread(Callable &&func, Args &&...args): _t(std::forward<Callable>(func), std::forward<Args>(args)...) {}explicit joining_thread(std::thread t) noexcept: _t(std::move(t)) {}joining_thread(joining_thread &&other) noexcept: _t(std::move(other._t)) {}joining_thread &operator=(joining_thread &&other) noexcept{// 如果當前線程可匯合,則匯合等待線程完成再賦值if (joinable()){join();}_t = std::move(other._t);return *this;}joining_thread &operator=(joining_thread other) noexcept{// 如果當前線程可匯合,則匯合等待線程完成再賦值if (joinable()){join();}_t = std::move(other._t);return *this;}~joining_thread() noexcept{if (joinable()){join();}}void swap(joining_thread &other) noexcept{_t.swap(other._t);}std::thread::id get_id() const noexcept{return _t.get_id();}bool joinable() const noexcept{return _t.joinable();}void join(){_t.join();}void detach(){_t.detach();}std::thread &as_thread() noexcept{return _t;}const std::thread &as_thread() const noexcept{return _t;}
};
- 使用起來比較簡單,我們直接構造一個joining_thread對象即可。
4. 容器存儲
void use_vector() {std::vector<std::thread> threads;for (unsigned i = 0; i < 10; ++i) {threads.emplace_back(param_function, i);}for (auto& entry : threads) {entry.join();}
}
5. 選擇運行數量
- 借用C++標準庫的std::thread::hardware_concurrency()函數,它的返回值是一個指標,表示程序在各次運行中可真正并發的線程數量.我們可以模擬實現一個并行計算的功能,計算容器內所有元素的和
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include<algorithm>
#include<numeric>template<typename lterator,typename T>
struct accumulate_block
{void operator()(lterator first, lterator last, T& result){result += std::accumulate(first, last, result);}
};template <typename Iterator, typename T>
T parallel_accumulate(Iterator first, Iterator last, T init)
{unsigned long const length = std::distance(first, last);if (!length)return init; // ?-- - ① 上面的代碼1處判斷要計算的容器內元素為0個則返回。unsigned long const min_per_thread = 25;unsigned long const max_threads =(length + min_per_thread - 1) / min_per_thread; // 等價于ceil(length / min_per_thread) 向上取整// ?-- - ② 2處計算最大開辟的線程數,我們預估每個線程計算25個數據長度。unsigned long const hardware_threads =std::thread::hardware_concurrency(); // 表示程序在各次運行中可真正并發的線程數量./*但是我們可以通過std::thread::hardware_concurrency返回cpu的核數,我們期待的是開辟的線程數小于等于cpu核數,這樣才不會造成線程過多時間片切換開銷。*/unsigned long const num_threads =std::min(hardware_threads != 0 ? hardware_threads : 2, max_threads); // ?-- - ③ 3處計算了適合開辟線程數的最小值。unsigned long const block_size = length / num_threads; // ?-- - ④ 4處計算了步長,根據步長移動迭代器然后開辟線程計算。std::vector<T> results(num_threads);std::vector<std::thread> threads(num_threads - 1); // ?-- - ⑤ 5處初始化了線程數-1個大小的vector,因為主線程也參與計算,所以這里-1.Iterator block_start = first;for (unsigned long i = 0; i < (num_threads - 1); ++i){ // 6處移動步長,7處開辟線程,8處更新起始位置。Iterator block_end = block_start;std::advance(block_end, block_size); // ?-- - ⑥// 定義在頭文件 <iterator> 中。它的作用是 將一個迭代器向前或向后移動指定的距離。threads[i] = std::thread( // ?-- - ⑦accumulate_block<Iterator, T>(),block_start, block_end, std::ref(results[i]));block_start = block_end; // ?-- - ⑧}accumulate_block<Iterator, T>()(block_start, last, results[num_threads - 1]); // ?-- - ⑨9處為主線程計算。for (auto& entry : threads)entry.join(); // ?-- - ⑩10 處讓所有線程joinreturn std::accumulate(results.begin(), results.end(), init); // ?-- - ? 11 處最后將所有計算結果再次調用std的accumulate算出結果。
}
void use_parallel_acc()
{std::vector<int> vec(1000000);for (int i = 0; i < 1000000; i++){vec.push_back(i);}int sum = 0;sum = parallel_accumulate<std::vector<int>::iterator, int>(vec.begin(),vec.end(), sum);std::cout << "sum is " << sum << std::endl;
}int main()
{use_parallel_acc();return 0;
}
6. 識別線程
所謂識別線程就是獲取線程id,可以根據線程id是否相同判斷是否同一個線程。比如我們啟動了一個線程,我們可以通過線程變量的get_id()獲取線程id。
std::thread t([](){std::cout << "thread start" << std::endl;
});
t.get_id();
- 但是如果我們想在線程的運行函數中區分線程,或者判斷哪些是主線程或者子線程,可以通過這總方式
std::thread t([](){std::cout << "in thread id " << std::this_thread::get_id() << std::endl;std::cout << "thread start" << std::endl;
});
std::this_thread::get_id()
t.get_id()