Давайте подумаем как реализовать
паттерн Active Object в С++ (точнее в С++0x в рамках MSVC2010)
Реализация должна быть эффективна, проста и универсальна.
Также надо придумать как измерять производительность того или иного решения.
Я вижу следующий сценарий работы Active Object'а — в основном потоке у него быстро и часто раз вызываются методы, в фоновом потоке они выполняются.
Соответственно получаются два критерия — время добавления метода в очередь, и время выполнения очереди.
Для начала — две штуки на IOCP и asio (из буста 1.46.1)
#1
// iocp_ActiveObject.h
#pragma once
#include <cassert>
#include <functional>
#include <process.h>
#include <Windows.h>
namespace iocp_based {
class ActiveObject
{
public:
ActiveObject()
{
iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
assert(iocp != 0 && "ActiveObject ctor");
hThread = (HANDLE)_beginthreadex(nullptr, 0, threadFn, this, 0, nullptr);
assert(hThread != 0 && "ActiveObject ctor");
}
~ActiveObject()
{
if(hThread != 0)
stop();
}
template<typename F>
void post(F&& f)
{
auto fn = new std::function<void()>(std::forward<F>(f));
::PostQueuedCompletionStatus(iocp, 0, 0, (LPOVERLAPPED)fn);
}
protected:
void stop()
{
::PostQueuedCompletionStatus(iocp, 0, 1, 0);
::WaitForSingleObject(hThread, INFINITE);
::CloseHandle(hThread);
hThread = 0;
::CloseHandle(iocp);
iocp = 0;
}
virtual void on_exception(std::exception*)
{
assert(!"Unhandled exception in ActiveObject");
}
private:
ActiveObject(const ActiveObject&);
void operator=(const ActiveObject&);
static unsigned __stdcall threadFn(void* param)
{
((ActiveObject*)param)->threadMethod();
return 0;
}
void threadMethod()
{
for(;;)
{
DWORD unused1;
ULONG_PTR terminated;
LPOVERLAPPED ptr;
auto ok = ::GetQueuedCompletionStatus(iocp, &unused1, &terminated, &ptr, INFINITE);
assert(ok && "ActiveObject GetQueuedCompletionStatus() failed");
if(terminated != 0)
return;
auto f = (std::function<void()>*)ptr;
try
{
(*f)();
}
catch(std::exception& e)
{
on_exception(&e);
}
catch(...)
{
on_exception(nullptr);
}
delete f;
}
}
HANDLE hThread;
HANDLE iocp;
};
} // ns
#2
// asio_win7_ActiveObject.h
#define _WIN32_WINNT 0x0601 // Win7 (WinXP yields same results)
#include <memory>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
namespace asio_win7 {
class ActiveObject
{
public:
ActiveObject()
: ios()
, work(new boost::asio::io_service::work(ios))
, terminated(false)
{
thread.reset(new boost::thread([this]{ threadMethod(); }));
}
~ActiveObject()
{
if(!terminated)
stop();
}
template<typename F>
void post(F&& f)
{
ios.post(f);
}
protected:
void stop()
{
post([this]{ terminated = true; });
work = nullptr;
thread->join();
}
virtual void on_exception(std::exception*)
{
assert(!"Unhandled exception in ActiveObject");
}
private:
ActiveObject(const ActiveObject&);
void operator=(const ActiveObject&);
void threadMethod()
{
while(!terminated)
{
try
{
ios.run();
}
catch(std::exception& e)
{
on_exception(&e);
}
catch(...)
{
on_exception(nullptr);
}
}
}
boost::asio::io_service ios;
std::unique_ptr<boost::asio::io_service::work> work;
std::unique_ptr<boost::thread> thread;
bool terminated;
};
}
и код для тестирования
#include "asio_win7_ActiveObject.h"
#include "iocp_ActiveObject.h"
#include <iostream>
#include <intrin.h>
#include <Windows.h>
const auto TEST_CYCLES = 20;
const auto POST_CYCLES = 200;
template<typename ActiveObject>
__declspec(noinline)
void run(ActiveObject& ao)
{
ao.post([](){});
}
template<typename ActiveObject>
__int64 measure(ActiveObject& ao)
{
__int64 total = 0;
for(auto i = POST_CYCLES; i != 0; --i)
{
::Sleep(0);
auto start = __rdtsc();
run(ao);
auto stop = __rdtsc();
total += stop - start;
}
return total;
}
template<typename ActiveObject>
void test(ActiveObject& ao)
{
for(auto i = TEST_CYCLES; i != 0; --i)
{
auto eplased = measure(ao);
volatile bool finished = false;
std::cout << (eplased / POST_CYCLES) << ", ";
ao.post([&](){ finished = true; });
do ::Sleep(10); while(!finished);
}
std::cout << '\n' << std::endl;
}
int main()
{
::SetThreadAffinityMask(GetCurrentThread(), 1);
{
std::cout << "ASIO Win7" << std::endl;
asio_win7::ActiveObject ao;
test(ao);
}
{
std::cout << "IOCP" << std::endl;
iocp_based::ActiveObject ao;
test(ao);
}
}
логично, что производительность у них одинаковая 2000-5000 тактов, однако разброс огромен, в т.ч. между запусками приложения