C++线程池实现原理
作者:网络转载 发布时间:[ 2016/5/12 11:29:44 ] 推荐标签:测试开发技术 .NET
背景
多线程编程是C++开发者的一个基本功, 但是很多开发者都是直接使用公司给包装好的线程池库, 没有去了解具体实现,有些实现也都因为高度优化而写得讳莫如深,让初学者看得吃力。
所以写这篇文章主要是想以非常简单的方式讲讲实现原理, 希望初学者看完之后不是觉得「不明觉厉」,而是觉得「原来如此」。
面朝代码
首先先来一段超级简单(注释丰富)的代码展示多线程编程的经典写法。
注: 该段代码和完整运行示例请见 limonp-thread-pool-programming-example , 可以通过以下命令跑通示例代码和输出结果,建议尝试以下。当然记得前提是该机器的网络可以访问 GitHub 的情况下。 可能因为在中国访问 GitHub 并不是很稳定,不是每次都能成功,所以如果卡住的话多make几次。
git clone https://github.com/yanyiwu/practice
cd practice/cpp/limonp-v0.5.1-demo
make
#include "limonp/ThreadPool.hpp"
#include "limonp/StdExtension.hpp"
using namespace std;
const size_t THREAD_NUM = 4;
// 这个类本身没什么实际含义,只是为了示例多线程编程而写出来的而已。
class Foo {
public:
// 这个类成员函数Append会在多个线程中被调用,多个线程同时对 chars 这个类成员变量进行写操作,所以需要加锁保证线程安全。
void Append(char c) {
limonp::MutexLockGuard lock(mutex_);
chars.push_back(c);
}
string chars; // 多线程共享的对象
limonp::MutexLock mutex_; // 线程锁
};
void DemoClassFunction() {
Foo foo;
cout << foo.chars << endl;
// 初始化一个线程池。
limonp::ThreadPool thread_pool(THREAD_NUM);
thread_pool.Start(); // 启动线程池
for (size_t i = 0; i < 20; i++) {
char c = i % 10 + '0';
// 使用 NewClosure 绑定 foo 对象和 Append 函数和对应参数,构造一个闭包扔进线程池中运行,关于这个 NewClosure 后面会讲。
thread_pool.Add(limonp::NewClosure(&foo, &Foo::Append, c));
}
thread_pool.Stop(); // 等待所有线程工作(工作是指NewClosure生成的闭包函数)都完成,然后停止所有线程。
cout << foo.chars << endl;
}
int main() {
DemoClassFunction();
return 0;
}
上面代码注释已经非常详细,一路看下来可能困惑的应该是 NewClosure 闭包创建函数。 很多人谈到闭包觉得是一个很高深或者是坑很深的问题,但是此闭包非JavaScript的闭包。 是一个简单版本,只考虑值拷贝,不考虑引用类型的参数。
limonp::NewClosure(&foo, &Foo::Append, c)
所以在该代码中三个参数,第一个是类对象的指针,第二个是成员函数的指针,第三个是int,都是值传递。 在此不支持引用传递的参数。
简单闭包实现
假设你现在已经运行了刚才的这三行命令。
git clone https://github.com/yanyiwu/practice
cd practice/cpp/limonp-v0.5.1-demo
make
一切运行正常的话,应该目前所在的目录里面有 limonp-0.5.1 这个目录。 然后可以在
limonp-0.5.1/include/limonp/Closure.hpp
文件中看到 NewClosure 函数的定义,NewClosure 是一个模板函数,所以才能支持多种多样的类型。 但是也让代码变得晦涩很多。
但是 NewClosure 的实现原理主要的是生成一个满足 ClosureInterface 接口定义的对象而已。
同时这个 ClosureInterface 也异常的简单,如下:
class ClosureInterface {
public:
virtual ~ClosureInterface() {
}
virtual void Run() = 0;
};
因为当这个 Closure 对象被扔进线程池之后,其实是进入了一个队列中。 然后线程池的多个线程去从队列中获取Closure指针,然后调用该 Run() 函数。
注意到,NewClosure 其实是生成一个对象指针,我们只是构造了这个对象,但是没有销毁它? 是否会造成内存泄露? 显然不会,原因是在线程池中,调用完 Closure 中的 Run() 函数之后, 会 delete 该指针,所以不会造成内存泄露。
到这里只剩下一个关键的困惑点是 Run() 的具体实现。 也是如何通过如下代码将 Append 这个类成员函数变成一个实现了 Run() 函数的 Closure 对象?
limonp::NewClosure(&foo, &Foo::Append, c)
因为 NewClosure 是模板函数,支持各种参数类型,但是对于本文的例子,实际上调用的 NewClosure 函数实现如下:
template<class R, class Obj, class Arg1>
ClosureInterface* NewClosure(Obj* obj, R (Obj::* fun)(Arg1), Arg1 arg1) {
return new ObjClosure1<Obj, R (Obj::* )(Arg1), Arg1>(obj, fun, arg1);
}
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11