cpp11实现线程池(六)——线程池任务返回值类型Result实现

news2024/11/28 8:51:21

介绍

提交任务函数submitTask中返回的Result类型应该是用Result类包装当前的task,因为出函数之后task即如下形式:return Result(task);

ResultTask都要互相持有对方的指针,Task要将任务执行结果通过Result::setVal(run()) 调用传给其对应Result对象。

Result类的声明和实现如下

// threadpool.h
// 提交到线程池的task任务执行完成后的返回值类型
class Result
{
public:
	Result(std::shared_ptr<Task> task, bool isValid = true);
	~Result() = default;

	//获取任务执行后的返回值
	void setVal(Any any);

	// get方法,用户调用这个方法获取task的返回值
	Any get();
private:
	Any any_; // 存储任务的返回值
	Semaphore sem_; // 线程通信
	std::shared_ptr<Task> task_; // 指向对应获取返回值的任务对象
	std::atomic_bool isValid_; // 检查返回值是否有效
};

// threadpool.cpp
Result::Result(std::shared_ptr<Task> task, bool isValid)
	: isValid_(isValid)
	, task_(task)
{
	task->setResult(this);
}

//获取任务执行后的返回值
void Result::setVal(Any any)
{
	// 存储task的返回值
	this->any_ = std::move(any);  // Any 类只提供右值拷贝和移动赋值函数
	sem_.post(); // 获取任务的返回值,增加信号量资源
}

// get方法,用户调用这个方法获取task的返回值
Any Result::get()
{
	if (!isValid_)
	{
		return "";
	}
	sem_.wait(); // task任务如果没执行完,这里会阻塞用户线程
	return std::move(any_); // Any不提供左值拷贝构造和拷贝赋值
}

Task类的声明和实现如下

// threadpool.h
class Task
{
public:
	Task();
	~Task() = default;
	void exec();
	void setResult(Result* res);
	// 用户可自定义任务类型,从Task派生出来,重写run方法,实现自定义任务处理
	virtual Any run() = 0;
	
private:
	// 不要用智能指针,可能会导致和Result产生循环引用的问题
	Result* result_; // Result对象生命周期 > Task对象生命周期
}; 

// threadpool.cpp
Task::Task()
	: result_(nullptr)
{}
void Task::exec()
{
	if (nullptr != result_)
	{
		result_->setVal(run()); // !!!
	}
	
}

void Task::setResult(Result* res)
{
	result_ = res;
}

Master-Slave线程模型

Master线程用来分解任务,然后给各个Slave分配任务;

等待各个Slave线程执行完任务,返回结果;

Master线程合并各个任务结果并输出

测试

使用3个线程来计算1+2+…+300000000 的和(1加到3亿)

测试代码如下:

#include <iostream>
#include <chrono>
#include "threadpool.h"

using uLong = unsigned long long;

class MyTask : public Task
{
public:
	MyTask(uLong /*int*/ begin, uLong /*int*/ end)
		: begin_(begin)
		, end_(end)
	{}

	Any run()
	{
		std::cout << "tid:" << std::this_thread::get_id() << " begin!" << std::endl;
		//std::this_thread::sleep_for(std::chrono::seconds(5));
		uLong sum = 0;
		for (uLong i = begin_; i <= end_; i++)
		{
			sum += i;
		}
		std::cout << "tid:" << std::this_thread::get_id() << " end!" << std::endl;
		
		return sum;
	}
private:
	uLong /*int*/ begin_;
	uLong /*int*/ end_;
};
int main()
{
	ThreadPool pool;
	pool.start(4);
	
	Result res1 = pool.submitTask(std::make_shared<MyTask>(1, 1000000000));
	Result res2 = pool.submitTask(std::make_shared<MyTask>(1000000001, 2000000000));
	Result res3 = pool.submitTask(std::make_shared<MyTask>(2000000001, 3000000000));
	uLong sum1 = res1.get().cast_<uLong>();
	uLong sum2 = res2.get().cast_<uLong>();
	uLong sum3 = res3.get().cast_<uLong>();

	// Master - Slave线程模型
	std::cout << (sum1 + sum2 + sum3) << std::endl;
	
	getchar();
	return 0;
}

在这里插入图片描述

出现的问题

测试时最后一个获取任务的线程,没有执行完成

在这里插入图片描述

而且,似乎出现了死锁的现象,按任意键无反应

分析

根据卡住的位置,我简单的推测是第三个任务执行的问题,于是在提交第三个子任务处打上断点

在这里插入图片描述

到达断点后进入MyTask构造函数,在初始化列表发现end_成员被初始化为负数(如下图)

在这里插入图片描述

这是由于int类型的最大值在编译器中定义如下

#define INT_MAX    2147483647

小于3000000000,导致了溢出,从而转变成负数。

这样导致在如下的任务执行函数的循环中,程序永远无法出循环

在这里插入图片描述

解决办法

MyTask类中的成员类型改变为unsigned long long

class MyTask : public Task
{
public:
	MyTask(uLong /*int*/ begin, uLong /*int*/ end)
		: begin_(begin)
		, end_(end)
	{}

	Any run()
	{
		std::cout << "tid:" << std::this_thread::get_id() << " begin!" << std::endl;
		//std::this_thread::sleep_for(std::chrono::seconds(5));
		uLong sum = 0;
		for (uLong i = begin_; i <= end_; i++)
		{
			sum += i;
		}
		std::cout << "tid:" << std::this_thread::get_id() << " end!" << std::endl;
		
		return sum;
	}
private:
	uLong /*int*/ begin_;
	uLong /*int*/ end_;
};

程序运行后结果正确

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/550872.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RestCloud新一代(智能)全域数据集成平台发布

5月18日&#xff0c;RestCloud在其成立六周年的当天&#xff0c;发布了“新一代&#xff08;智能&#xff09;全域数据集成平台”。 5月18日&#xff0c;RestCloud在其成立六周年的当天&#xff0c;发布了“新一代&#xff08;智能&#xff09;全域数据集成平台”。 根据业内专…

【Linux环境基础开发工具】软件包管理器-yum

写在前面 今天我打算介绍如何在Linux环境下载软件&#xff0c; Linux作为一个操作系统&#xff0c;就像windows一样&#xff0c;当然是存在软件的。 目录 写在前面 怎么在Linux环境安装软件 源代码安装 rpm安装包安装 yum安装 如何理解Linux的生态 如何使用yum安装软…

【LLM大模型】模型和指令微调方法

note Hugging Face 的 PEFT是一个库&#xff08;LoRA 是其支持的技术之一&#xff0c;除此之外还有Prefix Tuning、P-Tuning、Prompt Tuning&#xff09;&#xff0c;可以让你使用各种基于 Transformer 结构的语言模型进行高效微调。AIpaca羊驼&#xff1a;让 OpenAI 的 text-…

今年测试工程师正遭【革命】,“点点工”如何破局?

近几年来的特殊情况&#xff0c;综合过去的大形势变化&#xff0c;所有行业都会自下而上的进行一轮技术“大清洗”&#xff0c;技术停滞不前的“点工”或将被逐步取代。 软件测试现状 测试行业在十几年间发生了翻天覆地的变化&#xff0c;从早期站在风口上的快速发展&#xff…

fastapi基础篇

文章目录 简介环境搭建安装基础文件自动文档 基础使用POST请求传递参数返回定制信息jinja2返回html 简介 FastAPI 是一个用于构建 API 的现代、快速&#xff08;高性能&#xff09;的 web 框架&#xff0c;使用 Python 3.6 并基于标准的 Python 类型提示。 关键特性 快速&#…

【学习笔记】TCP/IP协议详解

1.A、B、C类网络号各有多少个&#xff1f; A类网络号&#xff1a;共有2^7 - 2个&#xff0c;即126个。这是因为A类网络号的第一个字节范围是1.0.0.0到126.0.0.0&#xff0c;其中0.0.0.0和127.0.0.0是特殊保留地址&#xff0c;不能用于网络划分。 B类网络号&#xff1a;共有2^…

Redis的主从复制,哨兵及群集

一、主从复制 1、主从复制-哨兵-集群 主从复制&#xff1a;主从复制是高可用Redis的基础&#xff0c;哨兵和集群都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份&#xff0c;以及对于读操作的负载均衡和简单的故障恢复。缺陷&#xff1a;故障恢复无法自…

免费通过微软Edge浏览器使用ChatGPT的手把手详细教程

ChatGPT是OpenAI推出的人工智能语言模型&#xff0c;能够通过理解和学习人类的语言来进行对话&#xff0c;像人类一样进行聊天交流&#xff0c;甚至还可以生成图片、编写代码。下面介绍如何通过在微软Edge浏览器的扩展程序中使用ChatGPT。 一、使用效果 ChatGPT的使用效果示例…

一文读懂!RK3668和RK3568有什么区别?

​ 从上图可以看出&#xff0c;RK3568和RK3566 CPU均为四核Cortex-A55架构&#xff0c;GPU为Mali-G522EE&#xff0c;内置NPU&#xff0c;可提供1T算力&#xff0c;支持DDR及CPU Cache全链路ECC等&#xff0c;RK366与RK3568最大区别的是RK3568具有PCIe接口、双千兆以太网和更…

基于FPGA+SDRAM+BT656视频解码移植总结

一、硬件准备 1、TVP5150模块(模拟视频信号解码模块)。 2、模拟摄像头一个(PAL或NT格式输出AV同轴) 3、FPGA开发板一块(EP4CE6+SDRAM+VGA) 实现功能: 模拟摄像头输出的视频信号为模拟信号,AV 同轴线缆输出,通过转接线接 到 TVP5150 模块,FPGA 控制 TVP5150 模块,…

C语言学习分享(第八次)------初阶指针

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C语言学习分享⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多C语言知识   &#x1f51d;&#x1f51d; 初阶指针 1. 前言&#x1f6a9;2. …

TypeScript 之 Lambda 函数

本文作者为 360 奇舞团前端开发工程师 TypeScript 之 Lambda 函数 Lambda 函数 又称箭头函数 箭头函数表达式语法比函数表达式语法更简短&#xff0c;并且没有自己的this&#xff0c;arguments,super或new.target。箭头函数表达式更适用于那些本来需要匿名函数的地方&#xff0…

揭秘物联网平台设备管理核心!Java代码示例对比,一篇文章全知道!

《高并发系统实战派》-- 值得拥有 一、 设备管理模块的意义 设备管理模块是物联网平台的核心模块之一&#xff0c;主要负责设备的接入、注册、管理、监控等工作&#xff0c;是构建物联网平台的基础。通过设备管理模块&#xff0c;可以实现对设备的资源动态管理、设备状态实时…

哈希环如何用在直播调度系统

背景 直播CDN系统通常用L1或者L2的缓存集群&#xff0c;缓解中心服务器压力。缓存集群需要满足2个条件 写&#xff1a;同一份数据写在一台缓存CDN服务器上&#xff0c;至少是同一节点上&#xff1b;读&#xff1a;对于这份数据的读取&#xff0c;能尽快索引到缓存CDN服务器上…

[CTF/网络安全] 攻防世界 baby_web 解题详析

[CTF/网络安全] 攻防世界 baby_web 解题详析 index.html & default.htmlindex.phpHTTP 302总结 题目描述&#xff1a;想想初始页面是哪个 index.html & default.html 初始页面的文件名一般为 index.html 或 default.html。这两个文件名都是 Web 服务器默认的首选文件…

shell编程--变量

变量 在shell中用户可以建立变量来存储数据&#xff0c;但不支持数据类型&#xff0c;变量名命名规则&#xff1a;数字、字母、下划线&#xff0c;不能以数字开头。 环境变量 当前shell的环境设置的一些变量 ​ export—设置新的环境变量 ​ env—显示所有环境变量 ​ set—…

Codeforces Round 874 (Div. 3)

作者&#xff1a;指针不指南吗 专栏&#xff1a;codeforces &#x1f43e;或许会很慢&#xff0c;但是不可以停下来&#x1f43e; 文章目录 A. Musical PuzzleB. Restore the WeatherC. Vlad Building Beautiful Array A. Musical Puzzle Problem - 1833A - Codeforces 题意 …

<Python实际应用>做一个简单的签到投屏系统

公司接了个活&#xff0c;承办一个由团委组织的五四青年节徒步活动&#xff0c;其中一个环节是现场报名&#xff0c;来的人把名字填进去后随机分组&#xff0c;并显示在现场的LED大屏幕上&#xff0c;我自告奋勇用Python来开发这个小程序。这里记录一下 【项目需求】 1、报名…

数据结构初阶(3)(链表:链表的基本概念、链表的类型、单向不带头非循环链表的实现、链表的相关OJ练习、链表的优缺点 )

接上次博客&#xff1a;和数组处理有关的一些OJ题&#xff1b;ArrayList 实现简单的洗牌算法&#xff08;JAVA)(ArrayList&#xff09;_di-Dora的博客-CSDN博客 目录 链表的基本概念 链表的类型 单向、不带头、非循环链表的实现 遍历链表并打印节点值&#xff1a; 在链…

华为OD机试真题 Java 实现【关联端口组合并】【2023Q1 100分】

一、题目描述 有M (1<M<10)个端口组&#xff0c;每个端口组是长度为N(1<N<100)的整数数组&#xff0c;如果端口组间存在2个及以上不同端口相同&#xff0c;则认为这两个端口组互相关联&#xff0c;可以合并。 第一行输入端口组个数M&#xff0c;再输入M行&#x…