基于C++ std::thread 的线程池

设计思路

算了,直接上代码,自己看吧。

/*******************************************************
 * @FileName: Task.h
 * @Author: xxxx
 * @CreatedTime: Jul 17th 2020
 * @Description:
 *		Wrapped thread pool task.
********************************************************/
#ifndef __TASK__H___	// macro name too short, so make it a little complicated.
#define __TASK__H___

#if defined _WIN32 || defined _WIN64
#  ifndef WIN32
#    define WIN32
#  endif
#endif

// For corssplatform
#ifdef WIN32
#define TASKAPI		__stdcall
#else
#define TASKAPI
#endif

#include <iostream>
typedef void* THPHANDLE;
#define	THP_NULL_HANDLE	(nullptr)
class Task
{
public:
	virtual int run() = 0;
	virtual ~Task() = default;
};
typedef int(*CBFct_t)(void*);
typedef void(*CBArgFree_t)(void*);
int TASKAPI THP_Intialize(THPHANDLE * ph, int sz);
int TASKAPI THP_Unitialize(THPHANDLE h);
int TASKAPI THP_PushTask(THPHANDLE h, Task* t);
int TASKAPI THP_PushTask(THPHANDLE h, CBFct_t cb, void* arg = nullptr, CBArgFree_t argfree = nullptr);
#endif	// !__TASK__H___
/*******************************************************
 * @FileName: ThreadPool.h
 * @Author: xxxxx
 * @CreatedTime: Jul 17th 2020
 * @Description:
 *		Base thread pool tool.
********************************************************/
#ifndef __THREAD_POOL_H__
#define __THREAD_POOL_H__

#include "Task.h"

#include <thread>
#include <mutex>
#include <condition_variable>
#include <deque>
#include <atomic>

class CallbackTask : public Task
{
public:
	typedef int(*CallbackFct)(void*);
	typedef void(*CallbackTaskArgFree)(void*);
	CallbackTask(CallbackFct cb, void* arg, CallbackTaskArgFree cbfree = nullptr);
	virtual int run() override;
	~CallbackTask();
private:
	CallbackFct m_cb{ nullptr };
	CallbackTaskArgFree m_cbFree{ nullptr };
	void * m_arg{ nullptr };
};

class ThreadPool
{
public:
	/* 构造函数 已被私有化,该类只能通过工厂方法,实现堆中动态对象的获取, delete this 的深层来源 */
	/* 友元函数实现 创建 */
	friend ThreadPool* alloc_pool();
	friend void free_pool(ThreadPool* pool);
	/* static public 成员方式 实现创建 */
	static ThreadPool* create();
	void destroy();

	int init(int sz);
	int fini();
	int push(Task* p_task);

protected:
	int pop(Task** pp_task);
	bool is_empty();

private:
	std::atomic<bool>			m_inited{false};
	std::atomic<bool>			m_exit{false};
	std::deque<std::thread*>	m_pool{};
	std::deque<Task*>			m_que{};
	std::mutex					m_mtx{};
	std::condition_variable		m_cond{};

private:
	static void task_cb_fct(void* arg);

	// you must use create static method to get a threadpool instance
	ThreadPool() = default;
	~ThreadPool() = default;

	ThreadPool(ThreadPool const&) = delete;
	ThreadPool& operator=(ThreadPool const&) = delete;
};

#endif // !__THREAD_POOL_H__
/*******************************************************
 * @FileName: ThreadPool.cpp
 * @Author: xxxxxxx
 * @CreatedTime: Jul 17th 2020
 * @Description:
 *		Base thread pool tool.
********************************************************/
#include "ThreadPool.h"

int TASKAPI THP_Intialize(THPHANDLE * ph, int sz)
{
	if (ph == nullptr)
	{
		return -1;
	}
	ThreadPool* p = ThreadPool::create();
	if (p == nullptr)
	{
		return -2;
	}
	int ret = p->init(sz);
	if (ret != 0)
	{
		p->destroy();
		return -3;
	}
	*ph = p;
	return 0;
}

int TASKAPI THP_Unitialize(THPHANDLE h)
{
	if (h == THP_NULL_HANDLE)
	{
		return -1;
	}
	ThreadPool* p = (ThreadPool*)h;
	int ret = p->fini();
	if (ret != 0)
	{
		//p->destroy();
		//return ret;
	}
	p->destroy();
	return 0;
}

int TASKAPI THP_PushTask(THPHANDLE h, Task* t)
{
	if (h == THP_NULL_HANDLE)
	{
		return -1;
	}
	ThreadPool* p = (ThreadPool*)h;
	return p->push(t);
}

int TASKAPI THP_PushTask(THPHANDLE h, CBFct_t cb, void* arg, CBArgFree_t argfree)
{
	if (h == THP_NULL_HANDLE)
	{
		return -1;
	}
	ThreadPool* p = (ThreadPool*)h;
	return p->push(new CallbackTask(cb, arg, argfree));
}

CallbackTask::CallbackTask(CallbackFct cb, void* arg, CallbackTaskArgFree cbfree) : m_cb(cb), m_arg(arg), m_cbFree(cbfree)
{
}

int CallbackTask::run()
{
	int ret = 0;
	if (m_cb)
	{
		ret = m_cb(m_arg);
	}
	if (m_cbFree != nullptr && m_arg != nullptr)
	{
		m_cbFree(m_arg);
	}
	
	return ret;
}

CallbackTask::~CallbackTask()
{
}

ThreadPool * alloc_pool()
{
	return new (std::nothrow) ThreadPool;
}

void free_pool(ThreadPool * pool)
{
	if (pool)
	{
		delete pool;
	}
}

ThreadPool * ThreadPool::create()
{
	ThreadPool * pool = new (std::nothrow) ThreadPool();
	if (pool == nullptr)
	{
		// OOM ERRROR....
		return nullptr;
	}
	return pool;
}

void ThreadPool::destroy()
{
	if (!m_inited.load() && m_exit.load())
	{
		// Some logic here
		delete this;
	}
	else
	{
		delete this;
	}
}


int ThreadPool::init(int sz)
{
	if (sz < 1)
	{
		return -1;
	}

	if (m_inited.load())
	{
		return -2;
	}
	/**
	 * be careful, must set init flag true ASAP.
	**/
	m_inited.store(true);

	m_exit.store(false);
	std::thread* thrd = nullptr;
	for (int i = 0; i < sz; i++)
	{
		thrd = new (std::nothrow) std::thread(task_cb_fct, this);
		if (thrd == nullptr)
		{
			return -3;
		}
		m_pool.push_back(thrd);
	}
	return 0;
}

int ThreadPool::fini()
{
	if (!m_inited.load())
	{
		return -1;
	}
	/**
	 * be careful, must set exit flag true ASAP, so all the work thread can exit ASAP.
	**/
	m_exit.store(true);

	for (auto & thrd : m_pool)
	{
		m_cond.notify_all();
		if (thrd != nullptr && thrd->joinable())
		{
			thrd->join();
			delete thrd;
			thrd = nullptr;
		}
	}

	/**
	 * be careful, must set inited flag false after all the thread handled the task.
	**/
	m_inited.store(false);
	return 0;
}

int ThreadPool::push(Task * p_task)
{
	if (p_task == nullptr)
	{
		return -1;
	}
	// if not inited or if exited, cannot push any task.
	if (!m_inited.load() || m_exit.load())
	{
		return -2;
	}
	std::unique_lock<std::mutex>	lock_guard(m_mtx);
	m_que.push_back(p_task);
	m_cond.notify_one();
	return 0;
}

int ThreadPool::pop(Task ** pp_task)
{
	if (pp_task == nullptr)
	{
		return -1;
	}
	if (!m_inited.load())
	{
		return -2;
	}
	std::unique_lock<std::mutex>	lock_guard(m_mtx);
	if (m_que.empty())
	{
		m_cond.wait(lock_guard);
	}
	if (m_que.empty())
	{
		*pp_task = nullptr;
		return -3;
	}
	*pp_task = m_que.front();
	m_que.pop_front();
	return 0;
}

bool ThreadPool::is_empty()
{
	std::unique_lock<std::mutex>	lock_guard(m_mtx);
	return m_que.empty();
}

void ThreadPool::task_cb_fct(void* arg)
{
	ThreadPool *pool = (ThreadPool*)arg;
	Task* task = nullptr;
	int ret = 0;
	while (true)
	{
		if (pool->m_exit.load() && pool->is_empty())
		{
			break;
		}
		ret = pool->pop(&task);
		if (ret == 0 && task != nullptr)
		{
			ret = task->run();
			if (ret != 0)
			{
				//
			}
			delete task;
			task = nullptr;
		}
	}
}

main.cpp

#include <iostream>
#include <sstream>
#include <thread>
#include <string>
#include <cstdio>
#include <cstdlib>

#include "Task.h"

/**
 * @Descrition:
 *		template method, object to string.
 * @Param: t of type t, must overload operator<< const method.
 * @Return: std::string type value.
**/
template<typename T>
std::string objectToString(T const& t)
{
	std::ostringstream stream;
	stream << t;
	return stream.str();
}

class TestTask: public Task
{
public:
	TestTask(std::string const& s = ""): m_msg(s) {}
	virtual int run()
	{
		m_msg += objectToString(std::this_thread::get_id());
		std::cout << m_msg << std::endl;
		std::this_thread::sleep_for(std::chrono::seconds(1));
		return 0;
	}
private:
	std::string m_msg{};
};


int io_test(void* arg)
{
	if (arg == nullptr)
	{
		/* maybe this is an error case,
		 * return error code,
		 * but you cannot get this error code.
		 * only you can do is to log this error.
		 */
		 // log_error("...............");
		return -1;
	}
	int * pn = (int*)arg;
	std::string s = "io test ";
	s += std::to_string(*pn);
	std::cout << s << std::endl;
	return 0;
}

/*
 * why need free callback??
 * if the arg were a class instance which is new-ed,
 * "delete void*" op won't call dctor.
 * 
 * free to malloc
 * delete to new
 * delete[] to new[].
***/
void io_test_arg_free(void* arg)
{
	if (arg == nullptr)
	{
		return;
	}
	int * pn = (int*)arg;
	delete pn;
}

int work_test(void* arg)
{
	if (arg == nullptr)
	{
		return -1;
	}
	std::string s = "work test: ";
	s += std::to_string(*((int*)arg));
	std::cout << s << std::endl;
	return 0;
}

void work_test_arg_free(void* arg)
{
	if (arg == nullptr)
	{
		return;
	}
	free(arg);
}

int threadpool_test()
{
	THPHANDLE h_io = THP_NULL_HANDLE;
	THPHANDLE h_worker = THP_NULL_HANDLE;
	int ret = THP_Intialize(&h_io, 2);
	if (ret != 0)
	{
		THP_Unitialize(h_io);
		exit(-1);
	}
	ret = THP_Intialize(&h_worker, 4);
	if (ret != 0)
	{
		THP_Unitialize(h_worker);
		exit(-1);
	}

	for (int i = 0; i < 2; i++)
	{
		THP_PushTask(h_io, new TestTask("IO Thread: "));
	}
	
	int * pm = new int{10};
	THP_PushTask(h_io, io_test, pm, io_test_arg_free);

	for (int i = 0; i < 8; i++)
	{
		THP_PushTask(h_worker, new TestTask("Worker: "));
	}

	int * pn = (int *)malloc(sizeof(int));
	*pn = 50;
	THP_PushTask(h_worker, work_test, pn, work_test_arg_free);

	THP_Unitialize(h_io);

	THP_Unitialize(h_worker);

	return 0;
}

int main()
{
	threadpool_test();
	return 0;
}

具体用法在main.cpp 中给出了示例。
可以继承 Task 接口,实现 Run() 函数,然后new,push。注意,task必须是new出来的才可以,因为框架会 delete,必须这么干。否则出问题后果自负。
第二,task不接受参数,挂callback 函数是以 void* 方式传入。框架会根据情况,自动释放这个参数。这个地方的释放,是需要小心的。所以,必须要自己挂载释放的callback。具体为什么呢?就是 new / delete, delete[] 以及 malloc / free 的区别了。
总之,最核心的一句话,就是 delete void*型的指针,出现的问题就是不会调用析构函数,就可能会造成内存泄露。
第三,这个task在一个que里面,线程去抢占。
其实还可以另外的方式,就是每个线程一个que,生产者线程去实现分发。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值