KMThreadPool: 4 – Thread Pool Step 2: SettingUp the Threads

本文详细介绍了如何创建一个线程池管理器,包括线程的创建、任务的分配和执行,以及线程的结束流程。通过封装KMTask类来管理任务数据,确保资源的有效利用和线程的安全运行。

4 – Thread Pool Step 2: SettingUp the Threads

KMTask

Lets go back to the heart of thethread pool (or to those in denial, the liver): The tasks. To keep everythingmanaged and such, we’ve encapsulated the KMTaskFunc and IKMTaskData* inside the KMTask class, just so we can keep things managed(and modular; so you can add new functionality like dependencies or referencesor contexts or whatever you’d like in the future).

class KMTask
{
private:
         KMTaskFunc         m_task;
         IKMTaskData*         m_data;
public:
         KMTask(KMTaskFunc task, IKMTaskData* data)
         {
                  m_task = task;
                  m_data = data;
         }
         ~KMTask()
         {
                  if(m_data!=NULL)
                       delete m_data;
                  m_data = NULL;
                  m_task = NULL;
         }
         KMTaskFunc         GetTask()         { return m_task; }
         IKMTaskData*         GetData()         { return m_data; }
protected:
};

Not so bad. Just manages the datafor us. This way we don’t really have to explicitly delete the IKMTaskData* inside our task.

KMThread

Here is the logic behind ourthreads: 1. Create the thread when the threadpool starts up, 2. Idle until wefind a task in the thread pool, 3. Run the task, 4. When we finish, clean upthe memory, 5. Goto 2 (6. Beat up the guy who wrote this tutorial for using agoto).

Here is a quick rundown of themembers in the threads:

KMThreadpool*          m_pthreadpool;
 
HANDLE                 m_hthread;
KMLock                 m_lock;
KMTask*                m_ptask;
 
bool                   m_brunning;
bool                   m_bpaused;
unsigned int           m_uithreadID;
DWORD                  m_dwexit;

m_pthreadpool is apointer to the actual thread pool class, and you’ll see why we need that injust a minute.
m_hthread is a handle (a kind ofsmart pointer) to the thread in the Windows API.
m_lock is the oneone lock we need to protect executing our task with.
m_ptask is thetask we get from the thread pool.
m_brunning is a flagthat’s set to true once the thread initializes successfully, and false if itfails or gets shut down.
m_bpaused is… uhh…legacy code! (meaning: code I forgot to delete, but isn’t breaking anything)
m_uithreadID is anID assigned from the Windows API.
m_dwexit is theexit code returned once the thread shuts down for any reason.

Now there are a few accessorfunctions and such, but we want to focus on 4 main functions:

public:
         void Begin();
         void End();
         DWORD ThreadProc();
protected:
         static unsigned __stdcall cThreadProc(LPVOID _pThis)
         {
                  return ((KMThread*)_pThis)->ThreadProc();
         }

Now, you see cThreadProc(). This function is called by the WindowsAPI when you create the thread. The API will be calling this function, and thisfunction calls ThreadProc(), and ThreadProc() runs our task. Indirection out thewazzoo, baby!

Next we have Begin(). Nothing too special here for anyone whohas ever created threads before. We grab the instance of our thread pool(because it’s a singleton), then we create our thread using _beginthreadex(), and if m_hthread isn’t NULL, then it was a success! Once again, verysimple.

void KMThread::Begin()
{
         // Set our thread pool
         m_pthreadpool = KMThreadpool::getInstance();
#if defined( _WIN32 )
         // Start the thread.
         m_hthread = (HANDLE)_beginthreadex( NULL,
                  0,
                  &cThreadProc,
                  (void*)this,
                  0,
                  &m_uithreadID );
         m_brunning = true;
         if( m_hthread == NULL )
         {
                  // You can add extra error-handling here.
                  m_brunning = false;
         }
#endif /* defined( _WIN32 ) */
}

And for every Begin(), there must be an End().

void KMThread::End()
{
#if defined( _WIN32 )
         if( m_hthread != NULL )
         {
                  m_brunning = false;
                  WaitForSingleObject( m_hthread, INFINITE );
                  DWORD ExitCode;
                  GetExitCodeThread( m_hthread, &ExitCode );
                  m_dwexit = ExitCode;
                  CloseHandle( m_hthread );
                  m_hthread = NULL;
         }
#endif /* defined( _WIN32 ) */
}

People familiar with somemultithreading experience may throw up a red flag and say “Hey! Why aren’t youusing _endthreadex()? Duh!” Well, Mr. NonBeliever, _endthreadex() in the Win32 API does not closethe handle. This explicit way of closing the thread allows me to free up allnecessary memory and allows me to get the exit codes and such. And for newbiesto threading, that WaitForSingleObject() function is also part of the Win32 API.It waits for the handle to the thread to finish its current cycle (for an INFINITE amount of time), and stops it fromcontinuing so we can manipulate the handle (in this case, closing the handle).

The last major function in thisclass is the full-on assembily of the circulatory system of the thread pool:The ThreadProc() function. This function is in charge of processing the actual task andgrabbing a new task once we finish. However, it’s not as simple as just run thetask and get a new one from the queue. We have to be safe and slick when we dothis. Here’s a look at the code:

DWORD KMThread::ThreadProc()
{
         m_ptask = NULL;
         // The main thread-loop. As long as this loop
         // is running, the thread stays alive.
         while(m_brunning)
         {
                  Sleep(1);
                  // The thread pauses when it finishes a task.
                  // Adding a task resumes it.
                  if(m_ptask != NULL)
                  {
                       m_lock.Lock();
                       {
                                KMTaskFunc task = m_ptask->GetTask();
                                IKMTaskData* data = m_ptask->GetData();
                                // Run the actual task
                                if(task != NULL && data != NULL)
                                {
                                     task(data);
                                }
                                // Task is complete.
                                delete m_ptask;
                                m_ptask = NULL;
                       }
                       m_lock.Unlock();
                  }
                  // If we're finished with our task, grab a new one.
                  if(m_ptask == NULL && m_pthreadpool->IsProcessing() == true)
                  {
                       m_ptask = m_pthreadpool->m_qtaskList.pop();
                  }
         }
         return 0;
}

The thread is almost set up asits own program, with the main thread loop in there. It’s set up so as long asthe thread is running (set by either begin(), end() or by the thread pool), it will do thework inside.
First thing we do inside this loop is Sleep(1). When I originally wrote the thread pool,I wasn’t calling Sleep(), and all my threads became processor hogs. If you downloaded the code orare writing it yourself, go ahead and comment out the Sleep() call and watch your computer burn!

Next, we check to see if the m_ptask is NULL.

  • If it isn’t, then we go ahead and process it. We lock around the tasks to protect the data from getting possibly modified by another thread, and after we call the task (like you would with any normal function) we go and clean up the data.
  • If it is, check the thread pool and get the next task in the queue.

And that’s our threads! Nifty!Now on to the main brain of the outfit… The interface for all thismultithreaded goodness…

 

转自:http://keithmaggio.wordpress.com/code/c-win32-thread-pool-manager/4-step2/

在开发过程中,如果遇到链接接口引用了 `Threads::Threads` 但提示找不到目标的问题,可能是由于以下几个原因导致的: 1. **命名空间或类名冲突** 如果 `Threads::Threads` 是一个类或命名空间的定义,确保该类或命名空间在项目中正确声明并包含在当前作用域中。例如,在 C++ 或 C# 中,如果类名与命名空间名相同,可能会导致解析器混淆引用路径。检查头文件或源文件中是否正确定义了 `Threads` 类或命名空间,并确认是否在当前文件中正确引入了相关命名空间。 2. **项目结构或依赖问题** 如果 `Threads::Threads` 是另一个项目或库中的组件,确保当前项目已正确引用该库,并且构建配置中包含了相应的依赖项。例如,在 Visual Studio 中,检查项目属性中的“引用”部分是否包含了定义 `Threads::Threads` 的项目或 DLL。 3. **编译器或 IDE 缓存问题** 有时 IDE(如 Visual Studio)可能未正确更新引用信息,尤其是在频繁修改项目结构或代码后。尝试清理并重新构建整个解决方案,或者重启 IDE 以刷新内部缓存和链接信息。 4. **拼写或大小写错误** 确保在接口或代码中引用的 `Threads::Threads` 拼写与定义完全一致,包括大小写。某些语言对标识符大小写敏感,如 C++ 和 Java,而 C# 则对命名空间和类型名敏感。 5. **链接器配置问题** 如果使用的是 C++,检查链接器设置是否包含必要的库文件(如 `.lib` 文件),并且在代码中是否使用了正确的 `#pragma comment(lib, "xxx.lib")` 指令或项目依赖配置。 6. **代码生成或自动引用问题** 如果 `Threads::Threads` 是由某种工具自动生成的类或接口,确保生成过程已完成且没有错误。某些 IDE 插件或代码生成工具可能会在构建过程中动态创建引用,若生成失败,可能导致引用丢失。 ### 示例代码片段(C++) ```cpp // Threads.h #pragma once namespace Threads { class Threads { public: void Start(); }; } // SomeOtherFile.cpp #include "Threads.h" void SomeFunction() { Threads::Threads threadInstance; threadInstance.Start(); } ``` ### 调试建议 - 使用 IDE 的“转到定义”功能查看 `Threads::Threads` 是否能正确跳转到其定义。 - 检查输出窗口或构建日志,确认是否有相关的编译或链接错误信息。 - 如果使用了数据库链接或其他远程接口,参考 `NETWORK_LINK` 参数的使用方式,确保源和目标数据库连接正常,并且接口定义与实际服务匹配[^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值