스레드 수에 따라 갈린다 — 스레드 2에서는 CONDITION_VARIABLE 이, 4 이상에서는 Kernel Object의 HANDLE Event 가 앞선다
some_event.cpp
#include <iostream>
#include <atomic>
#include <mutex>
#include <vector>
#include <memory>
#include <Windows.h>
template<typename Out>
void print_env(Out& out) {
#define PRINT_MACRO(VAR) out << #VAR ": " << VAR << std::endl
PRINT_MACRO(_MSC_FULL_VER);
PRINT_MACRO(_MSVC_LANG);
PRINT_MACRO(_WIN64);
PRINT_MACRO(_MT);
#ifdef _DEBUG
out << "with _DEBUG" << std::endl;
#else
out << "without _DEBUG" << std::endl;
#endif
PRINT_MACRO(_DLL);
#undef PRINT_MACRO
}
void exec_systeminfo() {
system("systeminfo | findstr /r \"^OS.버전\"");
system("systeminfo | findstr /r \"^시스템 종류\"");
}
using namespace std;
struct my_event_with_std {
bool state;
condition_variable cond;
my_event_with_std() : state(false) {}
my_event_with_std(const my_event_with_std&) = delete;
my_event_with_std(my_event_with_std&&) = delete;
my_event_with_std& operator=(const my_event_with_std&) = delete;
my_event_with_std& operator=(my_event_with_std&&) = delete;
template <typename T>
void set(T& l) {
this->cond.wait(l, [=] {return !this->state; });
this->state = true;
this->cond.notify_all();
}
template <typename T>
void reset(T& l) {
this->cond.wait(l, [=] {return this->state; });
this->state = false;
this->cond.notify_all();
}
};
struct my_event_with_api {
HANDLE handle;
bool state;
my_event_with_api(): handle(::CreateEvent(NULL, FALSE, FALSE, NULL)), state(false) {}
~my_event_with_api() {
if (handle != NULL) ::CloseHandle(handle);
}
template<typename T>
void set(T& l) {
if (state) {
while (state) {
l.unlock();
::WaitForSingleObject(handle, INFINITE);
l.lock();
}
state = true;
::ResetEvent(handle);
}
else {
state = true;
::SetEvent(handle);
}
}
template<typename T>
void reset(T& l) {
if (state) {
state = false;
::SetEvent(handle);
}
else {
while (state) {
l.unlock();
::WaitForSingleObject(handle, INFINITE);
l.lock();
}
state = false;
::ResetEvent(handle);
}
}
};
#include <queue>
#include <chrono>
using namespace std::chrono;
template<typename T>
size_t event_link_loop_mt(atomic_bool& stop, size_t C = 2) {
vector<unique_ptr<thread>> threads;
vector<unique_ptr<T>> events;
struct log_data {
high_resolution_clock::time_point tp;
thread::id tid;
size_t event_idx;
bool is_set;
};
queue<log_data> event_log;
auto log = [&](size_t event_idx, bool is_set) {
//event_log.emplace(log_data{high_resolution_clock::now(), this_thread::get_id(), event_idx, is_set});
};
auto start = high_resolution_clock::now();
for (size_t i = 0; i < C; ++i) {
events.emplace_back(make_unique<T>());
}
mutex m;
unique_lock<mutex> l(m);
for (size_t i = 0; i < C; ++i) {
events[i]->set(l);
log(i, true);
}
for (size_t i = 0; i < C - 1; ++i) {
threads.emplace_back(std::make_unique<thread>([&, i] {
while (! stop) {
unique_lock<mutex> l(m);
events[i]->set(l);
log(i, true);
events[i+1]->reset(l);
log(i+1, false);
}
}));
}
size_t count = 0;
while (! stop) {
events[0]->reset(l);
log(0, false);
events[C - 1]->set(l);
log(C - 1, true);
if (count + 1 == 0) throw runtime_error("count overflow");
++count;
}
events[0]->reset(l);
log(0, false);
l.unlock();
for (auto& pt : threads) {
pt->join();
}
while (!event_log.empty()) {
const auto& line = event_log.front();
cerr << "[" << duration_cast<nanoseconds>(line.tp - start).count() / 1000000000. << "]: " << "tid =" << line.tid << " event_idx=" << line.event_idx << " is_set=" << line.is_set << "\n";
event_log.pop();
}
cerr.flush();
return count;
}
template<typename T>
void test(const char* prefix) {
SYSTEM_INFO info;
::GetSystemInfo(&info);
for (int i = 1; i < static_cast<int>(info.dwNumberOfProcessors); ++i) {
T m;
atomic_bool stop = false;
size_t result = 0;
thread t([&] {
result = event_link_loop_mt<T>(stop, i + 1);
});
this_thread::sleep_for(3s);
stop = true;
t.join();
cout << prefix << i + 1 << "," << result << endl;
}
}
int main()
{
print_env(std::cout);
exec_systeminfo();
cout << "method,threads,count" << endl;
test<my_event_with_std>("my_event_with_std,");
test<my_event_with_api>("my_event_with_api,");
return 0;
}
복수 thread를 사용하여, 수주 연결한 Event로 대기로부터 시그널을 3초간 반복하여, 합계 시그널 회수(처음의 발신만 카운트)를 계측한다, 라고 하는 것이다. 3초 sleep 하고 멈추는 로직이므로 오차는 나름대로라고 생각합니다. 동기화 객체는 다음 두 가지입니다.
VC++2019
some_event_2019.log
_MSC_FULL_VER: 192930154
_MSVC_LANG: 201402
_WIN64: 1