手把手教你实现一个完美的线程池(可商用)第二集 c++高阶开发

手把手教你实现一个完美的线程池(可商用)第一集 c++高阶开发_杀神李的博客-CSDN博客

上文已经把整个线程类和仿函数搞的差不多了 现在我们来看具体实现

首先,由于系统API的要求,我们必须提供一个线程入口函数。

 static DWORD WINAPI ThreadEntry(LPVOID lpParam);

这个函数必须是一个 __stdcall 调用约定的函数,所以只能是静态 函数。 因为任何类的非静态成员函数,都是__thiscall 调用约定的函数。 所以这里只能是静态成员函数,以去除__thiscall 调用约定的影响。 当然,我们提供的真正的线程函数是下面这个函数:

 void EnterThread();//__thiscall

这两个函数是如何跳转的,我们可以看看它们的实现代码: 首先通过线程创建API创建线程:

m_hThread = CreateThread(
 NULL, 0, CThread::ThreadEntry,
 this, CREATE_SUSPENDED, &m_nThreadID);

线程入口函数设置为CThread::ThreadEntry这个静态函数。 线程的参数传入了this,也就是线程类自己的地址。 线程的初始状态是暂停状态,后面通过Start启动。 通过这种方式,Start的逻辑被统一了,任何时候都是恢复线程即 可。 无需区别是暂停后恢复的,还是初始化后恢复的了。 接下来就是入口函数了:

DWORD WINAPI GodKillerLi::CThread::ThreadEntry(LPVOID
lpParam)
{
 CThread* thiz = (CThread*)lpParam;
 thiz->EnterThread();
 ExitThread(THREAD_OK);
 return THREAD_OK;
}

这里的lpParam依据前面的逻辑,必然是上面我们标记出来的this参 数。 所以我们只需要把这个参数还原成CThread指针,即可调用对应的 EnterThread函数了。 这样一过渡,我们就成功的把静态成员函数转到成员函数。使得线 程的各种属性都可以发挥作用了。 线程函数EnterThread执行完成之后,我们需要执行ExitThread(THREAD_OK);来真正结束线程。 注意,后面的return THREAD_OK;其实并不会被执行到。 真正的线程主体函数如下:

void GodKillerLi::CThread::EnterThread()
{
 int ret = 0;
 m_bValid = true;
 do {
 while (m_pFunction == NULL) {
 if (m_bValid == false)return;
 Sleep(1);
 }
 CFunctionBase* func = m_pFunction;
 if (func == NULL)break;
 ret = (*func)();
 } while (ret == THREAD_AGAIN);
 m_bValid = false;
}

这个函数首先会等待自定义线程函数的设置。

while (m_pFunction == NULL) {
 if (m_bValid == false)return;
 Sleep(1);
 }

这一步主要是应对需要多次运行的线程函数而写的。 确定线程函数无恙之后,我们就会取出自定义线程函数的值:

CFunctionBase* func = m_pFunction;

取出之后,需要再次验证一下函数是否存在:

if (func == NULL)break;

这里是防止多线程环境下,其他线程通过线程对象接口,重置了自 定义线程函数,从而使得func为空。 这样取出成员变量到线程函数的本地变量之后,外面再怎么更改这 个成员属性,都不会影响到本次函数的运行了。 然后我们通过函数调用运算符,调用用户设置的函数,完成对线程 函数的调用:

ret = (*func)();

注意,返回值ret会影响后续的函数走向。 如果是返回THREAD_AGAIN,那么线程函数可能会被再次执行。 其他值,则终止线程函数,结束线程运行。

了解完核心逻辑,我们再来看看线程类的属性:

DWORD m_nThreadID;
 HANDLE m_hThread;
 bool m_bValid;//True表示线程正常 False表示线
程不可用
 std::atomic<CFunctionBase*> m_pFunction;

m_nThreadID 记录的是本线程的ID

m_hThread 记录的是本线程的句柄

m_bValid 记录的是本线程的状态,true表示线程正常(包括暂停 状态),false表示线程异常(未初始化、线程执行完成退出等)

m_pFunction 记录的是用户自定义函数,初始值为NULL。可以通 过构造函数和SetThreadFunc 来进行设置。 m_pFunction是原子类的对象。这样可以避免多线程环境下 SetThreadFunc 突然设置该属性,导致不可知的问题。 现在关于线程类的详细定义我们已经说完了。

具体实现如下

template< typename _FX, typename... _Types >
GodKillerLi::CThread::CThread(_FX _Func, _Types...
_Args)
 :m_pFunction(new CFunction<_FX, _Types...>
(_Func, _Args...))
{
 m_bValid = false;
 m_hThread = CreateThread(
 NULL, 0, CThread::ThreadEntry,
 this, CREATE_SUSPENDED, &m_nThreadID);
}
GodKillerLi::CThread::CThread() :m_pFunction(NULL)
{
 m_bValid = false;
 m_hThread = CreateThread(
 NULL, 0, CThread::ThreadEntry,
 this, CREATE_SUSPENDED, &m_nThreadID);
}
GodKillerLi::CThread::~CThread()
{
 Stop();
 CFunctionBase* func = m_pFunction;
 m_pFunction = NULL;
 delete func;
}
template<typename _FX, typename ..._Types>
int GodKillerLi::CThread::SetThreadFunc(_FX _Func,
_Types ..._Args)
{
 if (_Func == NULL) {
 m_pFunction = NULL;
 }
 else {
 m_pFunction = new CFunction<_FX,
_Types...>(_Func, _Args...);
}
 return 0;
}
int GodKillerLi::CThread::Start()
{
 if (m_pFunction == NULL) {
 return THREAD_IS_INVALID;
 }
 if ((m_hThread != NULL) && (m_bValid ==
false)) {
 DWORD ret = ResumeThread(m_hThread);
 if (ret == -1) {
 return ret;
 }
 }
 return THREAD_OK;
}
int GodKillerLi::CThread::Pause()
{
 if ((m_hThread != NULL) && (m_bValid ==
true)) {
 DWORD ret = SuspendThread(m_hThread);
 if (ret == -1) {
 return THREAD_PAUSE_ERROR;
 }
 }
 else {//线程无效
 return THREAD_IS_INVALID;
 }
 return THREAD_OK;
}
int GodKillerLi::CThread::Stop()
{
 m_bValid = false;//设计标志
if (m_hThread != NULL) {
 //析构的时候没有结束,则强制结束线程
 DWORD ret =
WaitForSingleObject(m_hThread, 10);
 if (ret == WAIT_TIMEOUT) {
 //尽量不要走到这一步来,否则在线程中new出
来的内存将失控!!!
 TerminateThread(m_hThread,
THREAD_IS_BUSY);
 }
 CloseHandle(m_hThread);
 m_hThread = NULL;
 }
 return THREAD_OK;
}
int GodKillerLi::CThread::Restart()
{
 if (m_pFunction == NULL) {//自定义函数有效
 return THREAD_IS_INVALID;
 }
 if (m_bValid != false || (m_hThread !=
NULL)) {
 //线程仍然在运行
 return THREAD_IS_BUSY;
 }
 m_hThread = CreateThread(
 NULL, 0, CThread::ThreadEntry,
 this, 0, &m_nThreadID);
 return THREAD_OK;
}
bool GodKillerLi::CThread::isValid() const
{
 return m_bValid && (m_pFunction != NULL);
}

部分已经展示的代码我已经去除了,大家可以看前面关于入口函数 和线程函数的定义。 这些代码的作用我就不赘述了,我只讲讲一些注意要点: 首先是关于构造函数。 我们可以看到,无论是带参数的构造函数还不带参数的构造函数, 我们都会把线程给创建出来。 一方面,线程真正的入口函数在线程自己内部,外部不可见。所以 我们想创建就可以尽早的创建。 另一方面,线程的创建消耗的资源要远高于其他对象的创建。所以 早点创建,等到后面Start的时候,就不会把所有消耗集中在一个点 上。这一方面有时候会导致程序对资源的需求呈现冲击波式的需 求,让系统的稳定性比较糟糕。 其次,在任何操作之前,一定要检查一下,操作所依赖的成员属性 的有效性。 因为我们是在多线程环境下进行编程,很多时候一些属性的变化是 发生在其他线程。本线程的程序逻辑是看不见变化的。 我们可以看到开始、暂停的时候,我们做了大量检测,尽量保证后 续操作的有效性。

接着我们来看看停止操作:

int GodKillerLi::CThread::Stop()
{
 m_bValid = false;//设计标志
 if (m_hThread != NULL) {
 //析构的时候没有结束,则强制结束线程
 DWORD ret =
WaitForSingleObject(m_hThread, 10);
 if (ret == WAIT_TIMEOUT) {
 //尽量不要走到这一步来,否则在线程中new出来
的内存将失控!!!
 TerminateThread(m_hThread,
THREAD_IS_BUSY);
 }CloseHandle(m_hThread);
 m_hThread = NULL;
 }
 return THREAD_OK;
}

停止操作之前,我们需要先把线程的状态设置为失效状态。 这样可以防止在停止期间,其他线程去操作Start、Pause等接口, 来干扰停止动作。 然后判断一下线程的状态。 如果线程已经不存在(可能是没有初始化,也可能是其他线程已经 调用了Stop接口),则直接返回成功。 另外,如果线程只要存在,我们就一定会结束线程。 当然,直接调用TerminateThread也不太合适。 所以我们给了10ms秒的时间给线程去结束自己。 如果线程没有能够结束自己,说明线程已经失控,处于卡死状态。 这个时候,只能强制结束线程。 至于给出的缓冲时间,并非固定的10ms。 这可以是一个动态调整的值,依据实际项目的情况而定。 时间长度可以是1ms到无穷大,都可以。 另外WaitForSingleObject可以用来检测线程句柄的状态。 如果返回WAIT_ABANDONED,则表示线程已经终止。 如果返回WAIT_OBJECT_0,则表示线程正常结束。 如果返回WAIT_TIMEOUT,则表示线程仍然在运行。 如果返回-1,则表示参数有问题。比如第一个参数值根本不是一个 句柄。 最后,Restart 函数描述了如何重启线程。

重启线程需要注意的几点:

1. 前一个线程函数仍然在运行的时候不能重启线程。这是因为线程 类只有一个线程句柄,如果启动多个线程,线程的结束会存在失 控的风险。比如这里,如果我们强制启动一个线程,哪怕前一个 线程还在执行。那么线程的句柄和ID就会更新,而前一个线程的 CloseHandle(m_hThread); m_hThread = NULL; } return THREAD_OK; } 11 12 13 14 15 句柄和ID会丢失。万一这个时候前一个线程无法结束,整个进程 结束的时候,可能会因为这个线程无法结束而卡死。

2. 重启之前应该判断一下自定义函数是否有效。如果自定义函数都 没有有效设置,那么重启也是无意义的。这个时候应该阻止这样 的行为出现。虽然自定义函数无效,不会导致线程函数本身崩 溃,但是对线程的执行逻辑来讲,是一种损害。

3. 重启的时候,线程状态不应该以暂停状态做为初始化状态。而是 应该直接就运行起来。 至此,我们现在完成了线程类的设计与实现。

敬请期待下集

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杀神李

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值