设计思路
算了,直接上代码,自己看吧。
/*******************************************************
* @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,生产者线程去实现分发。