多线程调用的封装技巧
时间:2022-05-16
本文章向大家介绍多线程调用的封装技巧,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
很多时候, 我们想把一项操作放入后台线程去执行, 可能是为了提高操作体验(UI表现的流畅), 或者是性能(充分利用多核的计算能力)等
为了方便, 我在这里先定义一个简化的线程模型:
- 所有的操作都定义为命令(Command)
- 后台线程监听一个命令队列, 如果有命令就执行, 没有就等待
- 如果收到结束通知, 则结束该线程
比如我们有两种操作:
void PrintA()
{
printf("thread[%x]: aaan", this_thread::get_id().hash());
}
void PrintB()
{
printf("thread[%x]: bbbn", this_thread::get_id().hash());
}
这两种操作会在后台线程去执行, 用代码表示如下:
enum CommandType
{
CommandA,
CommandB
};
atomic_int8_t IsOver = 0;
concurrent_queue<CommandType> CommandQueue;
void SendCommand(CommandType cmd)
{
CommandQueue.push(cmd);
}
atomic_int8_t IsOver = 0;
concurrent_queue<CommandType> CommandQueue;
void CommandThreadProc()
{
printf("thread[%x]: startn", this_thread::get_id().hash());
chrono::seconds time(1);
CommandType command;
while (IsOver == 0)
{
if (CommandQueue.try_pop(command))
{
switch (command)
{
case CommandA:
PrintA();
break;
case CommandB:
PrintB();
break;
}
}
this_thread::sleep_for(time);
}
printf("thread[%x]: endn", this_thread::get_id().hash());
}
运行:
int _tmain(int argc, _TCHAR* argv[])
{
printf("thread[%x]: startn", this_thread::get_id().hash());
thread t(CommandThreadProc);
SendCommand(CommandA);
SendCommand(CommandB);
this_thread::sleep_for(chrono::seconds(10));
IsOver = true;
t.join();
printf("thread[%x]: endn", this_thread::get_id().hash());
system("pause");
return 0;
}
Nebula3中使用的就是类似这样的模型, 把各种参数封装成Command, 发到后台线程去执行, 然后写一堆的swith-case去判断是什么命令, 再执行相应的操作 这种方式的好处就是简单, 而且也把操作细节隐藏在内部线程里了, 不过从编码的角度来看, 相当烦琐
改进一下, 把操作定义在外部, 然后把Command进行抽象, 这样可以免掉很多的条件判断和重复性编码:
struct Command
{
virtual void DoCommand() = 0;
};
atomic_int8_t IsOver = 0;
concurrent_queue<Command*> CommandQueue;
void SendCommand(Command* cmd)
{
CommandQueue.push(cmd);
}
void CommandThreadProc()
{
printf("thread[%x]: startn", this_thread::get_id().hash());
chrono::seconds time(1);
Command* command = nullptr;
while (IsOver == 0)
{
if (CommandQueue.try_pop(command))
{
command->DoCommand();
delete command;
command = nullptr;
}
this_thread::sleep_for(time);
}
printf("thread[%x]: endn", this_thread::get_id().hash());
}
这样定义后只需要派生抽象Command就好, 增加新的操作后台线程的代码无需变动:
struct CommandA : public Command
{
virtual void DoCommand() override
{
PrintA();
printf("t%sn", __FUNCTION__);
}
};
struct CommandB : public Command
{
virtual void DoCommand() override
{
PrintB();
printf("t%sn", __FUNCTION__);
}
};
/****************main****************/
SendCommand(new CommandA());
SendCommand(new CommandB());
/************************************/
但是这样还要是重复去定义很多个Command的子类. 在学习WPF时, 发现他们可以直接使用Dispatcher.BeginInvoke把某个函数发到后台去执行, 后来想了想, 其实就是把函数封装成对象发过去了. C++借助成员函数指针什么的也可以实现类似的机制:
struct FunctionCommand : public Command
{
function<void()> fun;
FunctionCommand(function<void()> f) : fun(f) {}
virtual void DoCommand() override
{
fun();
printf("t%sn", __FUNCTION__);
}
};
/****************main****************/
SendCommand(new FunctionCommand(PrintA));
SendCommand(new FunctionCommand(PrintB));
/************************************/
这个方案看起来已经挺完美了, 但是, 还是不够灵活, 因为如果是在现有代码上重构, 一样需要封装很多函数出来. 在阅读Unreal代码时发现, 里面用了几个很巧妙的宏, 可以把代码片段封装成对象, 这样就免去了定义函数的代码量. 简化一下代码就是这样:
#define SEND_COMMAND(TypeName, Code)
{
struct TypeName##Command : public Command
{
virtual void DoCommand() override
{
Code;
printf("t%sn", __FUNCTION__);
}
};
SendCommand(new TypeName##Command());
}
/****************main****************/
SEND_COMMAND(Print,
{
PrintA();
});
SEND_COMMAND(Print,
{
PrintB();
});
SEND_COMMAND(Print,
{
PrintB();
PrintA();
});
/************************************/
可以看到宏参数可以是多行的, 所以一段代码可以当成宏的一个参数传入, 然后封装成对象, 真是让人想不到的办法!
完整的main函数和执行结果如下:
int _tmain(int argc, _TCHAR* argv[])
{
printf("thread[%x]: startn", this_thread::get_id().hash());
thread t(CommandThreadProc);
SendCommand(new CommandA());
SendCommand(new CommandB());
SendCommand(new FunctionCommand(PrintA));
SendCommand(new FunctionCommand(PrintB));
SEND_COMMAND(Print,
{
PrintA();
});
SEND_COMMAND(Print,
{
PrintB();
});
SEND_COMMAND(Print,
{
PrintB();
PrintA();
});
this_thread::sleep_for(chrono::seconds(10));
IsOver = true;
t.join();
printf("thread[%x]: endn", this_thread::get_id().hash());
system("pause");
return 0;
}
- C#检测SqlServer中某张表是否存在
- Cobbler自动化批量安装linux服务器的操作记录
- Twemproxy——针对MemCached与Redis的代理
- 谁适合学Python?学了Python可以做什么工作?
- webservice今日遇到的二个问题:DataTable + Namespace
- php安全配置记录和常见错误梳理
- Flex:地图缩放平移效果(简易版)
- Mongodb副本集+分片集群环境部署记录
- 线上mongodb 数据库用户到期时间修改的操作记录
- 微信小程序“授权失败”场景的处理
- 动软.net代码生成器 win2008 r2下无法连接oracle,以及vs2008模板丢失的解决
- ASP.NET Web API 支持 CORS
- oracle odp.net 32位/64位版本的问题
- Redis+TwemProxy(nutcracker)集群方案部署记录
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 动态分组卷积-Dynamic Group Convolution for Accelerating Convolutional Neural Networks
- (15)Shell概述及脚本执行方式
- jvm线上内存问题排查
- (18)Bash输入输出重定向
- RPC 和 REST还有RESTFul到底是个什么玩意?
- 线程和线程池的几个状态值
- 阿里代码规约为什么不让使用Executors包装好线程池呢?
- Groovy、热部署和热加载(自定义类加载器)及spring loaded 部分源码分析
- Centos在线迁移到腾讯云cvm
- ClickHouse源码导读:网络IO
- 手把手教你:将ClickHouse集群迁至云上
- 直播中台iLiveSDK终端框架演变之路
- ClickHouse 数据导入实战:Kafka 篇
- pipeAsyncFunctions
- 【python-leetcode287-循环排序】寻找重复的数