多线程异步处理
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
就叫集中处理器吧,多线程应用或者需要异步处理的任务,我思考了这样的一个思路,将所有线程进行集中管理,而将任务提交给线程管理者来进行执行。优点显而易见,对于应用于不同机器中,线程数应该等于CPU核心的线程数,这样效能才能得到最大发挥,而且集中管理的线程更容易监视和调试。不过缺点也很明显,就是资源竞争问题,做个比如,如果集中处理器中只有一条服务线程,而两个任务被同时加入到了处理队列中而现执行的任务需要等待另一个任务的处理结果,而本身自己这个任务正在被处理占用了唯一的服务线程,至于服务器没有余力去处理它所依赖的另一个任务而造成锁死,其实就算两条线程或者更多处理线程也会存在这种问题,而这个设计并没解决这个问题。所以还是应该在实际应用中去自己注意不要发生这样的问题。恩,这里我的思路是与其去等待不如去触发,所以我在实际应用中会尽量去用触发机制而避免锁死线程去等待某个需要的资源。其实还有一种思路就是自己设计等待模型,在等待的过程中释放线程的占用,不过这个我没有写出来因为暂时还没有必要这么做因为一个合理的解决方案设计是不会发生资源竞争问题的,不过仔细想想做出来也满方便的,这个回头思考一下。
这是最初的版本,因为后来发现了boost所以就没再改进,恩,现在我在把自己所有的工具类在向boost靠拢,利用boost重新设计,这里最后我会介绍它的boost版本。
先贴代码,我想代码比起文字更能说明问题。首先是任务类:
class CTask
{
public:
CTask() {};
virtual ~CTask() {};
virtual void OnTransact() {};
// 这个函数不会被托管线程调用,应该由窗口收到完成消息后调用
virtual void OnCompletion(void*) {};
};
TRUSTEESHIP(CTask);
任务从这个基类继承,其中OnTransact虚函数是任务的实体,就是你要用这个任务要做的事情,OnCompletion虚函数其实不需要写,这是为了配合其他设计而做的接口,这里忽略就好,而且那不是个很好的设计所以就不多解释了。对了这里用了前一篇提到的智能指针。(TRUSTEESHIP宏)
集中处理类的声明:
class CTrusteeshipThread
{
public:
CTrusteeshipThread(UINT thrNumber = 0);
virtual ~CTrusteeshipThread();
BOOL AddTask(CTaskPtr task);
enum STATE_ENUM
{
STATE_Run, // 运行
STATE_StopPending, // 正在停止
STATE_Stopped // 停止
};
void SetNotifyWnd(HWND hWnd, LONG mess) { m_NotifyWnd = hWnd; m_Message = mess; }
protected:
BOOL CloseAllWorkThread(DWORD dwMilliseconds = INFINITE); // 关闭所有工作线程
BOOL CreateWorkThread(UINT thrNumber = 0); // 创建工作线程
#ifdef AF
X_TBEGIN
vector
#else
vector
#endif
STATE_ENUM m_State;
HWND m_NotifyWnd;
LONG m_Message;
HANDLE m_hTaskEvent;
CRITICAL_SECTION m_TaskCs;
vector
void UseStartTaskQueue() // 开始使用TaskQueue数组
{
::EnterCriticalSection(&m_TaskCs);
}
void UseEndTaskQueue() // 结束使用TaskQueue数组
{
::LeaveCriticalSection(&m_TaskCs);
}
CTaskPtr GetTask();
static DWORD WINAPI WorkerThread(LPVOID pParam);
static DWORD WINAPI WorkerThread_S(LPVOID pParam);
void Notify(CTaskPtr& task);
};
接口非常简单,构造函数的参数是线程池内的线程数,默认0会取CPU的线程数的倍数(其实我是从某本书上看来的,说这样能最大利用CPU资源)AddTask函数将任务添加处理器。SetNotifyWnd函数是为了支持前面说的设计,完全不用考虑。其实这个类可以设计的更好,比如根据任务的负载情况动态的调节线程数,因为当时需要解决的问题不存在这样精细的问题所以就没有写而日后也没有涉及到,不过设计之初还是考虑到这点了,恩,如果增加这个功能的话也完全不会破坏现有的接口和结构。另外一点就算动态调整线程数所能带来的性能优化其实是有限的,或者在大多数情况下会造成恶化,因为线程在睡眠的时候是不会占用CPU的时间片的,它只是一块内存而已,而增减线程的操作需要占用CPU还会重新分配内存,对于内存碎片等问题都需要考虑在内的。
贴它的实现:
// TrusteeshipThread.cpp: implementation of the CTrusteeshipThread class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "TrusteeshipThread.h"
#include "process.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CTrusteeshipThread::CTrusteeshipThread(UINT thrNumber)
{
TCHAR addr[100] = {0};
_itot_s((int)this, addr, 100, 10);
_tstring eventName = _T("CTrusteeshipThread:");
eventName += addr;
eventName += _T(":");
_itot_s((int)GetCurrentProcessId(), addr, 100, 10);
eventName += addr;
m_hTaskEvent = CreateEvent(NULL, TRUE, TRUE, eventName.c_str());
::InitializeCriticalSection(&m_TaskCs);
m_NotifyWnd = NULL;
CreateWorkThread(thrNumber);
}
CTrusteeshipThread::~CTrusteeshipThread()
{
CloseAllWorkThread();
CloseHandle(m_hTaskEvent);
::DeleteCriticalSection(&m_TaskCs);
}
CTaskPtr CTrusteeshipThread::GetTask()
{
// 如果队列为空进入等待,
::WaitForSingleObject(m_hTaskEvent, INFINITE);
while(TRUE)
{
if (m_State != STATE_Run)
{
return NULL;
}
UseStartTaskQueue();
for (vector
pos != m_TaskQueue.end(); pos++)
{
CTaskPtr task = *pos;
m_TaskQueue.erase(pos);
UseEndTaskQueue();
return task;
}
// 将事件设置为非受信
::ResetEvent(m_hTaskEvent);
UseEndTaskQueue();
::WaitForSingleObject(m_hTaskEvent, INFINITE);
}
}
BOOL CTrusteeshipThread::AddTask(CTaskPtr task)
{
UseStartTaskQueue();
m_TaskQueue.push_back(task);
UseEndTaskQueue();
// 将事件设置为受信
SetEvent(m_hTaskEvent);
return TRUE;
}
DWORD WINAPI CTrusteeshipThread::WorkerThread_S(LPVOID pParam)
{
UINT sehI = 0;
BEGIN:
sehI ++;
// __try
// {
return WorkerThread(pParam);
// }
// __except(EXCEPTION_EXECUTE_HANDLER)
// {
// if (sehI > 10)
// {
// return 1;
// }
// goto BEGIN;
// }
}
DWORD WINAPI CTrusteeshipThread::WorkerThread(LPVOID pParam)
{
CTrusteeshipThread* pTT = (CTrusteeshipThread*)pParam;
while (TRUE)
{
CTaskPtr task = pTT->GetTask();
if (task == NULL)
{
return 0;
}
task->OnTransact();
pTT->Notify(task);
}
return 0;
}
void CTrusteeshipThread::Notify(CTaskPtr& task)
{
if (m_NotifyWnd != NULL)
{
SendMessage(m_NotifyWnd, m_Message, (DWORD)&task, (DWORD)this);
}
}
BOOL CTrusteeshipThread::CloseAllWorkThread(DWORD dwMilliseconds)
{
m_State = STATE_StopPending;
// 将事件设置为受信,唤醒所有等待进程
SetEvent(m_hTaskEvent);
DWORD ThreadSize = m_WorkThreadList.size();
HANDLE* hHandleArray = new HANDLE[ThreadSize];
for (DWORD i = 0; i < ThreadSize; i++)
{
#ifdef AFX_TBEGIN
hHandleArray[i] = m_WorkThreadList[i]->m_hThread;
#else
hHandleArray[i] = m_WorkThreadList[i];
#endif
}
// 等待所有工作线程退出
if (WaitForMultipleObjects(ThreadSize, hHandleArray, TRUE, dwMilliseconds) == WAIT_TIMEOUT)
{
delete []hHandleArray;
return FALSE;
}
// 关闭句柄
for (DWORD i = 0; i < ThreadSize; i++)
{
::CloseHandle(hHandleArray[i]);
}
m_WorkThreadList.clear();
delete[] hHandleArray;
m_State = STATE_Stopped;
return TRUE;
}
BOOL CTrusteeshipThread::CreateWorkThread(UINT thrNumber)
{
if (!m_WorkThreadList.empty())
{
return FALSE;
}
if (thrNumber == 0)
{
SYSTEM_INFO SystemInfo;
GetSystemInfo(&SystemInfo);
thrNumber = SystemInfo.dwNumberOfProcessors * 2;
}
m_State = STATE_Run;
for (DWORD i = 0; i < thrNumber; i++)
{
#ifdef AFX_TBEGIN
CWinThread* hHandle = AfxBeginThread((AFX_THREADPROC)&WorkerThread_S, this);
if (hHandle != NULL)
{
hHandle->ResumeThread();
}
#else
HANDLE hHandle =
(HANDLE)_beginthreadex(NULL,
0,
(unsigned int (__stdcall *)(void *))&WorkerThread_S,
this,
0,
NULL);
#endif
if (hHandle == NULL)
{
return FALSE;
}
else
{
m_WorkThreadList.push_back(hHandle);
}
}
return TRUE;
}
在构造函数里会创建一个事件对象用来等待任务的到来。CreateWorkThread是创建工作线程的函数,工作线程的实体为WorkerThread函数,WorkerThread函数的工作就是调用GetTask函数从队列中获取任务然后调用任务的OnTransact成员。这里需要关心也就是GetTask成员函数了,这里要做好线程的同步问题。这个简单的类我就不多介绍了,因为暂时我是不会去用它了,现在更多的是用boost的版本,恩,其实更简单,就是对io_service的封装,因为我要实现的这个功能io_service完全实现了,如果早写时候接触boost的话我想我就不会写出这个类了。
boost的io_service类其实并不是直接支持这个集中处理功能的,io_service作为一个asio的一个核心组件,而并没有把它独立出来成为一个库,要直接用起来不是很方便,所以我进行了封装。io_service的性能我说不好,不过理论上应该是相当高的,甚至高于前面我的简单代码,说不好的原因是因为它的windows版本是利用的windows的iocp模型,也就是完成端口模型,详细请查阅CreateIoCompletionPort系列API函数吧,其实IOCP模型跟这篇文章所介绍的设计同出一辙,只是windows用来它来特别对待了IO问题。恩,windows也有自己的一套线程池概念,有兴趣的朋友可以翻阅相关资料,因为我现在不太喜欢依赖系统的设计所以没做过多考虑。简而言之可以说,io_service的windows版本完成了对windows的IOCP模型封装。
如果你想查阅io_service相关的介绍除了官方文档和io_service.hpp声明的注释对接口的介绍外,呢,其实都是英文的,虽然勉强能看看,但我确实没有能力熟读如流,所以当初想去了解他的时候我是直接追的源码。
准确的说io_service并不是一个完整的集中处理器,第一点它没有自己的处理线程,这个还需要我们写代码还生成线程调用它的run系列函数,第二点它的run系列处理函数如果内部没有了需要处理的任务会马上返回导致线程的结束。它其实只是一个任务集中器,将任务集中起来管理执行。
想要把它包装成我所想要的集中处理器有一个比较大的问题就是run系列函数的返回问题。解决这个问题要不就包装他的post函数要不就去深究它的实现找到理由(其实我并没有认真去读那些英文文档)。前一种显然不完美而且会造成这个io_service对象不能再做他本来的工作给asio的其他组件使用,这不是我想要的,所以我选择了后者,其实问题很简单,我用的是VC所以追的是他的WINDOWS实现boost.asio.detail.win_iocp_io_service里有一个计数器outstanding_work_,通过下内存断点又追过几步之后我发现了boost::asio::io_service::work类,boost::asio::
io_service::work的传入ios_service对象的构造函数会增加这个变量的值,恩,只要创建一个boost::asio::io_service::work的对象就能阻止这件事情的发生,而析构会使没有任务的的io_service对象的run全部返回,这实在太好了(其实官方文档有阐明)。恩,先讲一下io_service函数的用法吧。
第一种用法asio,asio库里有许多需要传入io_service对象的类,比如boost::asio::ip::tcp::socket Timers等等系列函数,其实一般来认为io_service对象是为此而生的,摘取boost文档的一段示例代码:
void handler(boost::system::error_code ec) { ... }
...
io_service i;
...
deadline_timer t(i);
t.expires_from_now(boost::posix_time::milliseconds(400));
t.async_wait(handler);
...
i.run();
这段代码的功能是400毫秒后运行handler方法。其中deadline_timer要实现它的任务需要一个io_service任务集中器,在时间到来的时候将handler扔进去执行。deadline_timer的expires_from_now方法从名字上就应该能明白他的作用,从现在起多久后执行,参数是一个time_type类型,其实这是一个模板参数,因为这篇文章并不是要详细讲解deadline_timer库所以这里不深入阐述了,这里只要传入一个boost::posix_time里一个代表时间段的对象就可以了,boost::posix_time也是一个非常方便的库,绝对时间点的代表,时间段的代表,时间运算都很方便。deadline_timer的async_wait方法表示了异步方式,时间到之后运行参数所指的handler函数,async_wait会马上返回,当时间到之后handler函数会被提交到io_service里去,所以这里需要某个线程来运行io_service的run函数来等待任务的到来,顺便说一下deadline_timer这些类对象会增加outstanding_work_的计数。其他需要传入io_service对象的方法模型都是相同的,都需要另外在某个线程里调用run函数。这是io_service的一般用法。
第二种用法,io_service作为一个任务集中器所提供的接口。这里只介绍两个最常用的post和run函数,这两个函数就能完成大部分任务。post是将要运行的任务投递给io_service对象,当然是异步的,实际运行是run函数负责的,这里只要创建出合理的线程来调用run函数等待处理到来的任务就可以了,当然这里需要注意如果io_service里没有了任务run函数会直接返回,而做一个boost::asio::io_service::work类的对象传入这个io_service对象就能阻止这件事情的发生,因此简单来说用法是这样的
boost::asio::io_service i;
boost::asio::io_service::work w(i);
// 线程函数
void worker_thread(void* param)
{
boost::asio::io_service *pIos = (boost::asio::io_service*)param;
pIos->run();
}
之后只要在需要异步处理的地方将任务post到io_service对象里就可以了。post函数依然是一个模板,参数官方文
档有说明:
Examples
A free function as a completion handler:
void completion_handler()
{
...
}
A completion handler function object:
struct completion_handler
{
...
void operator()()
{
...
}
...
};
A non-static class member function adapted to a completion handler using bind():
void my_class::completion_handler()
{
...
}
...
my_io_service.post(boost::bind(&my_class::completion_handler, this));
总结来说3种形式,无参无返回值的一般函数、函数对象、bind出来的function。恩,这里应该解释下bind库和function库,其实我是像单出一篇来讲解着两个库的,可是它的具体实现我没有深究就在这里解释下他的用法吧。其实只是没有必要所以就没追他的实现机理。bind其实是stl里bind2nd的扩展版本它支持更多的参数用起来更方便,而function则可以代表bind出来的结果,简单来说就是这样的。bind可以神奇的把任意多参数的函数变成任意多参数的函数,这个听一起来有点别扭,其实怎么说我只是知道它能做的事情而不知道它的原理,理由能这里要讲一下我的学习方法,我有很多很厚的书,但是这些书买来之后我只是记下了他们的目录并没有熟读所有内容,因为太不实际了,去花几年的时间去读呢,因此只要记下目录当需要到她的只是的时候再去读他的内容就可以了。这是一种非常有效率的办法。因此我这里只讲function和bind的部分功能怎么用。恩,用代码的形式。想了解更多自己参阅/doc/libs/1_55_0/libs/bind/bind.html和/doc/libs/1_55_0/doc/html/function.html吧。
Examples
Using bind with standard algorithms
class image;
class animation
{
public:
void advance(int ms);
bool inactive() const;
void render(image & target) const;
};
std::vector
template
{
c.erase(std::remove_if(c.begin(), c.end(), pred), c.end());
}
void update(int ms)
{
std::for_each(anims.begin(), anims.end(), boost::bind(&animation::advance, _1, ms));
erase_if(anims, boost::mem_fn(&animation::inactive));
}
void render(image & target)
{
std::for_each(anims.begin(), anims.end(), boost::bind(&animation::render, _1, boost::ref(target)));
}
Using bind with Boost.Function
class button
{
public:
boost::function
};
class player
{
public:
void play();
void stop();
};
button playButton, stopButton;
player thePlayer;
void connect()
{
playButton.onClick = boost::bind(&player::play, &thePlayer);
stopButton.onClick = boost::bind(&player::stop, &thePlayer);
}
恩,文档中这段示例代码能说明它能干的事情。不过就算要详细阐述它的用法也得用不少的篇幅,这里不是重点介绍它所有暂且
略过吧。
/doc/libs/1_55_0/doc/html/boost_asio/reference/io_service.html
回过头来继续说io_service,io_service::strand这也是需要提及的类,它的功能是把一系列的任务顺序整合为一个串来让io_service来管理,它明确了任务的顺序。用法如下
boost::asio::io_service i;
boost::asio::io_service::strand s(i);
i.post(s.wrap(任务方法a));
i.post(s.wrap(任务方法b));
i.post(s.wrap(任务方法c));
这样,无论有多少空闲线程在调用io_service.run,都会等到a执行完后再执行b再执行c,其实一般来说不是这样用的,因为完全可以再a里调用b再调用c。他是给boost::asio::ip::tcp::socket::async_read_some这些异步IO操作用的。
恩,差不多可以贴出集中处理器的代码了。
ThreadService.h:
#pragma once
#include
#include
#include
#include
class ThreadService
{
public:
ThreadService(void);
~ThreadService(void);
typedef boost::function
void Stop();
bool Begin(unsigned int nThreads = 0);
bool End(); // 结束, 永远等待
bool End(long waitSec); // 结束, 等待所设置的秒数
int Post(callback backFn);
boost::asio::io_service& GetIoService() { return m_Ios; }
protected:
void Run();
protected:
boost::asio::io_service m_Ios;
boost::asio::io_service::work *m_pWork;
std::list
boost::mutex m_Threads_mut;
};
ThreadService.cpp:
#include "ThreadService.h"
ThreadService::ThreadService(void)
{
m_pWork = NULL;
}
ThreadService::~ThreadService(void)
{
}
void ThreadService::Run()
{
boost::system::error_code ec;
std::size_t s = m_Ios.run(ec);
//std::size_t s = m_Ios.poll(ec);
}
void ThreadService::Stop()
{
m_Ios.stop();
}
bool ThreadService::End()
{
boost::mutex::scoped_lock lock(m_Threads_mut);
if (m_pWork == NULL)
{
return false;
}
delete m_pWork;
m_pWork = NULL;
for(std::list
{
(*it)->join();
delete *it;
}
m_Threads.clear();
return true;
}
bool ThreadService::End(long waitSec)
{
boost::posix_time::ptime td(boost::posix_time::microsec_clock::universal_time() + boost::posix_time::seconds(waitSec));
boost::mutex::scoped_lock lock(m_Threads_mut);
if (m_pWork == NULL)
{
return false;
}
delete m_pWork;
m_pWork = NULL;
for(std::list
{
if ((*it)->timed_join(td) == false)
{
return false;
}
delete *it;
it = m_Threads.erase(it);
}
return true;
//boost::asio::detail::event
}
bool ThreadService::Begin(unsigned int nThreads)
{
if (nThreads == 0)
{
nThreads = boost::thread::hardware_concurrency();
i
f (nThreads == 0)
{
return false;
}
}
boost::mutex::scoped_lock lock(m_Threads_mut);
if (m_pWork != NULL || m_Threads.empty() == false)
{
return false;
}
m_pWork = new boost::asio::io_service::work(m_Ios);
for (unsigned int i = 0; i < nThreads; i++)
{
//m_tg.create_thread(boost::bind(&CAsyncTriggerService::Run, this));
m_Threads.push_back(new boost::thread(boost::bind(&ThreadService::Run, this)));
}
return m_Threads.size() != 0;
}
int ThreadService::Post(callback backFn)
{
m_Ios.post(backFn);
return 0;
}
用法很简单:
ThreadService ts;
ts.Begin();
然后ts.Post任务就可以了。
最后调用ts.End等待全部执行结束就可以退出程序了。还可以通过GetIoService获取原生的io_service对象给asio的其他对象使用。