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;
}