C++併發編程實戰
基本說明
本文部分代碼來自於書籍"C++併發編程實戰",僅作本人學習之用。
文件說明:
- BackgroundTask.h 與 BackgroundTash.cpp:用來做使用類實例及其成員函數來創建線程的案例
- ThreadTest.h 與 ThreadTest.cpp: 相關關於標準庫線程相關的基礎知識的測試函數的聲明和定義
- ThreadGuard.h 與 ThreadGuard.cpp: 對線程對象進行包裝以應對發生異常時線程無法正常被join的情況
- main.cpp 測試ThreadTest中的接口函數
具體實現
BackgroundTask.h
#pragma once
class BackgroundTask
{
public:
void doWork();
private:
void subWork1();
void subWork2();
};
BackgroundTask.cpp
#include "BackgroundTask.h"
#include <iostream>
void BackgroundTask::doWork()
{
subWork1();
subWork2();
}
void BackgroundTask::subWork1()
{
for (int i = 0; i < 100; i++) {
std::cout << "i: " << i << "\n";
}
}
void BackgroundTask::subWork2()
{
for (char a = 'A'; a <= 'Z'; a++) {
std::cout << "character: " << a << "\n";
}
}
ThreadGuard.h
#pragma once
#include<thread>
// 對線程對象做封裝,保證ThreadGuard對象被析構的時候線程對象m_t直接調用join()
class ThreadGuard
{
public:
// explicit: 參數傳入時不允許發生隱式類型轉換
explicit ThreadGuard(std::thread& t) : m_t(t) {}
~ThreadGuard() {
// 對於給定的線程,join函數只能被調用一次,所以之前必須檢查其是否爲joinable
if (m_t.joinable()) {
m_t.join();
// 若調用的是detach,則會把線程丟在後臺運行,則無法等待該線程完成,同時也無法獲得對於該線程的引用以
// 便於後續操作該線程
}
}
// 確保編譯器不會自動提供拷貝構造函數和賦值運算符重載函數
ThreadGuard(ThreadGuard const&) = delete;
ThreadGuard& operator=(ThreadGuard const&) = delete;
private:
std::thread& m_t;
};
ThreadGuard.cpp
#include "ThreadGuard.h"
ThreadTest.h
#pragma once
#include<string>
#include<thread>
#include<vector>
#include<numeric> // std::accumulate
#include<functional> // std::mem_fn
#include<algorithm> // std::min std::for_each
#include<iostream>
// 調用類實例成員函數來創建線程
void createThreadByClassInstance();
//lambda表達式創建線程
void createThreadByLambda();
// 創建線程時向線程內部傳遞參數
void printParamter(int p[],int s);
void createThreadTransmitParameters();
// 引用失效 和 引用有效(其實就是值傳遞和引用傳遞的區別)
void changeArray(int p[], int s);
// 內部將數組元素傳遞到線程中
void threadTransimitParameterByValue();
void changeString(std::string& svalue);
void threadTransmitStringByRef();
// 線程所有權的轉移
void ftest1();
void ftest2();
void threadOwnership1();
void threadOwnership2();
void threadOwnership3();
// 對線程對象在外面包裝一個類,以處理外界程序發生異常情況而導致線程程序無法正常detach或者join的情況
void printAtoZ();
void printZeroTo99();
void ThreadGuardTest();
// 後臺運行的線程
void daemonThread();
// 運行時選擇線程數量
template<typename Iterator,typename T>
struct accumulate_block {
void operator()(Iterator first, Iterator last, T& ss){
ss = std::accumulate(first,last,ss); // 求給定範圍的元素的和,並指定初值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;
unsigned long const min_per_thread = 25;
// 長度26以下 使用1個線程
// 26及26以上,每滿26則增加一個線程
unsigned long const max_threads = (length + min_per_thread - 1) / min_per_thread; // 計算整除塊,要記住!!!
unsigned long const hardware_threads = std::thread::hardware_concurrency(); // 獲取硬件支持的線程數
std::cout << "硬件支持的線程數: " << hardware_threads << "\n";
// 硬件線程可用,取硬件線程數,否則取2
// 上述結果與max_threads取小,保證線程數量不會無限增大
unsigned long const num_threads = std::min(hardware_threads!=0?hardware_threads:2,max_threads);
// 根據數據長度計算需要提供給線程的每個處理塊的數據的大小
unsigned long const block_size = length / num_threads;
std::vector<T> results(num_threads); // 每個線程的執行結果
// 申請存儲每個線程的空間:vector(數量爲什麼比線程數少1:因爲最後一個線程分配得到的數據是不能被block_size整除的,所以最後一塊數據需要單獨處理)
std::vector<std::thread> threads(num_threads - 1);
Iterator block_start = first;
// 將數據分段使用num_threads - 1個線程進行處理,將結果保存在results中
for (unsigned long i = 0; i < (num_threads - 1); i++)
{
Iterator block_end = block_start;
std::advance(block_end, block_size);// 要前進的迭代器,要前進的元素數
threads[i] = std::thread(
accumulate_block<Iterator,T>(),
block_start,
block_end,
std::ref(results[i])
);
block_start = block_end;
}
// 從頭開始計算,不適用多線程
// 前面一個括號是對結構體accmulate_block的()進行重載後的調用形式
accumulate_block<Iterator, T>()(
block_start,
last,
std::ref(results[num_threads-1])
);
// 每個線程都執行join使得當前線程必須等待在當前線程中創建的線程執行完成
std::for_each(
threads.begin(),
threads.end(),
std::mem_fn(&std::thread::join) // 生成指向成員指針的包裝對象,可以存儲、複製、及調用指向成員指針
);
// T sum = std::accumulate(results.begin(),results.end()-1,0);
//T sum = 0;
// 打印出每一個線程的輸出結果
for (int i = 0; i < num_threads; i++) {
std::cout << "result[" << i << "]: " << results[i] << "\n";
//sum += results[i];
}
/*std::cout << sum <<" "<< results[results.size() - 1] << "\n";
std::cout << (sum == results[results.size() - 1] ? "true" : "false")<<"\n";*/
return std::accumulate( // 對所有線程計算出來的數據進行累加返回
results.begin(),
results.end(),
init
);
}
void parallel_accumulateTest();
// std::thread::id test
void threadIdTest();
ThreadTest.cpp
#include "ThreadTest.h"
#include "BackgroundTask.h"
#include <thread>
#include <iostream>
#include "ThreadGuard.h"
void createThreadByClassInstance()
{
BackgroundTask f;
// std::thread my_thread(f); // 編譯不通過
std::thread my_thread(&BackgroundTask::doWork, &f);
if (my_thread.joinable()) {
my_thread.join();
}
else {
std::cout << "thread cannot join!\n";
}
}
void createThreadByLambda()
{
// lambda表達式創建線程
std::thread t1([](std::string s) {
for (int i = 0; i < 100; i++) {
std::cout << s << "\n";
}
}, "leexiaolong"
);
if (t1.joinable()) {
t1.join();
}
else {
std::cout << "thread cannot join!\n";
}
}
void printParamter(int p[], int s)
{
for (int i = 0; i < s; i++) {
std::cout << p[i] << "\n";
}
}
void createThreadTransmitParameters()
{
const int sz = 128;
int buffer[sz];
for (int i = 0; i < sz; i++) {
buffer[i] = i;
}
std::thread t(printParamter, buffer, sz);
if (t.joinable()) {
t.join();
}
else {
std::cout << "thread cannot join!\n";
}
}
void changeArray(int p[], int s)
{
for (int i = 0; i < s; i++) {
p[i] = 100;
}
}
// 其實看傳進來的參數值是否會發生變化其實就是弄清楚 引用傳遞和值傳遞的區別即可
// 數組式的指針傳遞,線程會對數組元素的值進行修改
void threadTransimitParameterByValue()
{
const int sz=10;
int buffer[sz];
for (int i = 0; i < sz; i++) {
buffer[i] = i;
}
std::thread t(changeArray,buffer,sz);
t.join();
for (int i = 0; i < sz; i++) {
std::cout <<"change?: "<< buffer[i] << "\n";
}
}
void changeString(std::string& svalue)
{
std::cout << "origin: " << svalue << "\n";
svalue = "Hello world!";
std::cout << "new: " << svalue<<"\n";
}
void threadTransmitStringByRef()
{
std::string hello = std::string("hello CPP");
// std::thread t(changeString, hello); // 無法通過編譯:“std::invoke”: 未找到匹配的重載函數
std::thread t(changeString,std::ref(hello));
t.join();
std::cout << "After thread over: " << hello << "\n";
}
void ftest1()
{
for (int i = 0; i < 100; i++) {
std::cout << "i: " << i << "\n";
}
}
void ftest2()
{
for (char a = 'A'; a <= 'Z'; a++)
{
std::cout << a << "\n";
}
}
void threadOwnership1()
{
std::thread t1(ftest1);
std::cout <<"@@@@@@@@@@@@@@: "<< t1.get_id()<<"\n";
std::thread t2 = std::move(t1);
std::cout << "#############: " << t2.get_id()<<"\n";
t1 = std::thread(ftest2);
std::cout << "!!!!!!!!!!!!!: " << t1.get_id()<<"\n";
t1.join();
t2.join();
}
// 這個版本的代碼明顯會出現不正當程序行爲
void threadOwnership2()
{
std::thread t1(ftest1);
std::cout << "@@@@@@@@@@@@@@: " << t1.get_id() << "\n";
std::thread t2 = std::move(t1);
std::cout << "#############: " << t2.get_id() << "\n";
t1 = std::thread(ftest2);
std::cout << "!!!!!!!!!!!!!: " << t1.get_id() << "\n";
std::thread t3;
t1 = std::move(t3); // 此處會導致程序崩潰,必須等待接受線程對象的執行函數完成,即必須等待t1執行完成
t1.join();
t2.join();
}
// 針對threadOwnership2進行的修改
void threadOwnership3()
{
std::thread t1(ftest1);
std::cout << "@@@@@@@@@@@@@@: " << t1.get_id() << "\n";
std::thread t2 = std::move(t1);
std::cout << "#############: " << t2.get_id() << "\n";
t1 = std::thread(ftest2);
std::cout << "!!!!!!!!!!!!!: " << t1.get_id() << "\n";
std::thread t3;
t1.detach();
t1 = std::move(t3);
t2.join();
}
void printAtoZ()
{
ftest2();
}
void printZeroTo99()
{
ftest1();
}
void ThreadGuardTest()
{
std::thread t(printAtoZ);
ThreadGuard threadGuard(t);
printZeroTo99(); // 即使函數prinZeroTo99發生異常,線程對象t也能正確地被join
}
void daemonThread()
{
std::cout << "currentThread id: " << std::this_thread::get_id()<<"\n";
std::thread t(printAtoZ);
std::cout << "thread t id: " << t.get_id() << "\n";
if (t.joinable()) {
t.detach(); // 將線程t從當前線程中分離出來,使其作爲應用程序的後臺線程
}
}
void parallel_accumulateTest() {
//可以調整向量a的元素個數來查看程序運行變化情況
std::vector<int> a(26, 6); // 102是不能被25(這是parallel_accumulate中默認指定的分塊的大小)整除的
std::vector<int>::iterator begin = a.begin();
std::vector<int>::iterator end = a.end();
std::cout<<"final value: "<<parallel_accumulate(begin, end, 0)<<"\n";
}
void threadIdTest()
{
std::thread t1(printAtoZ);
std::thread t2(printZeroTo99);
// std::thread::id內部對比較運算符進行了重載,允許進行比較
std::thread::id id1 = t1.get_id();
std::thread::id id2 = t1.get_id();
// id1和id2取的是相同的線程對象
t1.join();
t2.join();
if (id1 == id2) {
std::cout << "getId: same!\n";
}
else {
std::cout << "getId: not same!\n";
}
std::thread::id id3 = t2.get_id();
if (id1 == id3) {
std::cout << "getId: same!\n";
}
else {
std::cout << "getId: not same!\n";
}
}
main.cpp
#include "BackgroundTask.h"
#include "ThreadTest.h"
/*
* crtdll.dll is a module containing standard C library functions such as printf, memcpy, and cos.
* crtdll.dll is a system process that is needed for your PC to work properly. It should not be removed.
*/
int main() {
/*createThreadByClassInstance();
createThreadByLambda(); */
// createThreadTransmitParameters();
// threadTransimitParameterByValue();
// threadTransmitStringByRef(); // 引用傳參給線程,線程對外界傳進來的變量進行了修改
//threadOwnership1();
// daemonThread();
// parallel_accumulateTest();// 累加並行計算
threadIdTest();
return 0;
}