C++併發編程實戰學習01

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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章