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