C++并行编程(一): 利用C++标准库实现仿Windows内核事件对象
2018-02-27 11:46
681 查看
我们知道,无论是Linux还是Windows,在底层的操作系统API中都提供了一些与线程间同步操作相关的内核对象,就以我最熟悉的Windows讲起,就提供了诸如临界区(关键段)、事件、互斥变量、条件变量、信号量、读写锁、旋转锁甚至原子操作等等的一系列方式,而Linux中也提供了类似的机制。由于这些底层机制在应用程序开发中经常使用,但在不同操作系统平台中调用的API又不一样,所有可见市面上有些C/C++开源项目对系统底层调用作了跨平台封装处理。所幸,C++官方也不甘落后,从C++11开始,在其标准库中也提供了系统无关的线程同步机制,其中主要是mutex和condition_variable两个类。
作为一贯使用Windows开发的C++人员,当你发现C++所提供的标准库中并没有包括Windows事件对象的高仿类时,是不是觉得很沮丧?其实无需懊恼,我们完全可以基于已有的mutex和condition_variable两个类来封装出仿Windows内核事件对象,这样一来,就算是跨平台的事件对象了。
其实在我写这篇文章时,我在工作中主要使用的语言还是C#,所以在编写C++代码时,代码和设计风格以及命名规范更多会偏向C#的方式。废话不多说,下面上代码(以下代码在VS2017中验证通过):
Event.h
仿照.NET中的层次设计,我编写了如下几个接口, 这几个接口都是纯虚的抽象类,没有任何的实现方法。#pragma once
#include <iostream>
#include <functional>
#include <memory>
#include <ratio>
#include <chrono>
using namespace std;
using namespace chrono;
namespace Threads
{
// 可等待句柄
class WaitHandle
{
public:
virtual ~WaitHandle();
// 等待事件直到为有信号状态
virtual void Wait() = 0;
// 等待事件直到为有信号状态或超时
virtual bool Wait(const milliseconds& time) = 0;
};
// 事件句柄
class EventHandle : public WaitHandle
{
public:
// 设置事件为有信号状态
virtual void Set() = 0;
// 释放事件对象
virtual void Close() = 0;
};
// 手动重置事件
class ManualResetEvent : public EventHandle
{
public:
// 创建事件对象
static unique_ptr<ManualResetEvent> CreateEvent();
public:
// 重置事件状态
virtual void Reset() = 0;
// 当前是否处于有信号状态
virtual bool IsSignaled() const = 0;
};
// 自动重置事件
class AutoResetEvent : public EventHandle
{
public:
// 创建事件对象
static unique_ptr<AutoResetEvent> CreateEvent();
};
}
EventImpl.h
这里是对上述接口的实现类定义,从设计的角度而言,用户不应该直接使用这个类,而是应该通过上述定义的CreateEvent方法中获得事件的实例对象。#pragma once
#include "Event.h"
#include <mutex>
#include <atomic>
#include <condition_variable>
namespace Threads {
// 手动重置事件
class ManualResetEventImpl : public ManualResetEvent
{
mutex m_mutex;
condition_variable m_cond;
volatile bool m_bSignal;
atomic<bool> m_bDisposed;
public:
// 初始化事件
ManualResetEventImpl();
// 设置事件为有信号状态
virtual void Set();
// 重置事件状态
virtual void Reset();
// 当前是否处于有信号状态
virtual bool IsSignaled() const;
// 等待事件直到为有信号状态
virtual void Wait();
// 等待事件直到为有信号状态或超时
virtual bool Wait(const milliseconds& time);
// 释放事件对象
virtual void Close();
private:
void CheckDisposed()const;
};
// 自动重置事件
class AutoResetEventImpl : public AutoResetEvent
{
mutex m_mutex;
condition_variable m_cond;
atomic<bool> m_bDisposed;
public:
AutoResetEventImpl();
// 设置事件为有信号状态
virtual void Set();
// 等待事件直到为有信号状态
virtual void Wait();
// 等待事件直到为有信号状态或超时
virtual bool Wait(const milliseconds& time);
// 释放事件对象
virtual void Close();
private:
void CheckDisposed()const;
};
}
Event.cpp
这里主要是提供了CreateEvent方法的实现,本质上就是构建一个实体对象。#include "Event.h"
#include "EventImpl.h"
#include <iostream>
#include <memory>
using namespace Threads;
using namespace std;
unique_ptr<ManualResetEvent> ManualResetEvent::CreateEvent()
{
unique_ptr<ManualResetEvent> ptr (new ManualResetEventImpl());
return ptr;
}
WaitHandle::~WaitHandle() = default;
unique_ptr<AutoResetEvent> AutoResetEvent::CreateEvent()
{
return unique_ptr<AutoResetEvent>(new AutoResetEventImpl());
}
EventImpl.cpp
这里是关键代码了!!代码不多,逻辑易懂,自己看↓#include "EventImpl.h"
using namespace Threads;
ManualResetEventImpl::ManualResetEventImpl()
:m_bSignal(false), m_bDisposed(false)
{
}
void ManualResetEventImpl::Set()
{
lock_guard<mutex> lock(m_mutex);
CheckDisposed();
if (!m_bSignal)
{
m_bSignal = true;
m_cond.notify_all();
}
}
void ManualResetEventImpl::Reset()
{
lock_guard<mutex> lock(m_mutex);
CheckDisposed();
m_bSignal = false;
}
bool ManualResetEventImpl::IsSignaled()const
{
CheckDisposed();
return m_bSignal;
}
void ManualResetEventImpl::Wait()
{
unique_lock<mutex> lock(m_mutex);
CheckDisposed();
m_cond.wait(lock, [this]{
return IsSignaled();
});
}
bool ManualResetEventImpl::Wait(const milliseconds & time)
{
unique_lock<mutex> lock(m_mutex);
CheckDisposed();
return m_cond.wait_for(lock, time, [this] {return IsSignaled(); });
}
void ManualResetEventImpl::Close()
{
auto expected = false;
if (!m_bDisposed.compare_exchange_weak(expected, true))
{
return;
}
lock_guard<mutex> lock(m_mutex);
m_cond.notify_all();
}
void ManualResetEventImpl::CheckDisposed()const
{
if (m_bDisposed.load() == true)
{
throw logic_error("ManualResetEventImpl Disposed.");
}
}
AutoResetEventImpl::AutoResetEventImpl()
:m_bDisposed(false)
{
}
void AutoResetEventImpl::Set() {
lock_guard<mutex> lock(m_mutex);
CheckDisposed();
m_cond.notify_all();
}
void AutoResetEventImpl::Wait()
{
{
unique_lock<mutex> lock(m_mutex);
CheckDisposed();
m_cond.wait(lock);
}
CheckDisposed();
}
bool AutoResetEventImpl::Wait(const milliseconds & time)
{
bool result;
{
unique_lock<mutex> lock(m_mutex);
CheckDisposed();
result = m_cond.wait_for(lock, time) == cv_status::no_timeout;
}
CheckDisposed();
return result;
}
void AutoResetEventImpl::Close()
{
auto expected = false;
if (!m_bDisposed.compare_exchange_weak(expected, true))
{
return;
}
lock_guard<mutex> lock(m_mutex);
m_cond.notify_all();
}
void AutoResetEventImpl::CheckDisposed() const
{
if (m_bDisposed.load() == true)
{
throw logic_error("AutoResetEventImpl Disposed.");
}
}
以上的代码提供了两种事件的定义实现,分别是手动重置事件(ManualResetEvent)和自动重置事件(AutoResetEvent),接下来的是Demo主程序的代码,演示如何使用ManualResetEvent。
main.cpp#include <cstdio>
#include <stdlib.h>
#include <iostream>
#include <memory>
#include "Event.h"
#include <thread>
using namespace std;
using namespace Threads;
using namespace chrono;
int main()
{
auto evt = ManualResetEvent::CreateEvent();
int input = 0;
thread t([&] {
try
{
// 等待事件
evt->Wait();
cout << "[Thread1] User Input :" << input << endl;
}
catch (const std::logic_error& e) // 事件对象被关闭
{
cerr << "[Thread1] error:" << e.what() << endl;
}
});
thread t2([&] {
try
{
// 等待事件,限时5秒
if (!evt->Wait(duration_cast<milliseconds>(seconds(5))))
{
// 5秒内都没完成
cout << "[Thread2] Wait time out;" << endl;
return;
}
cout << "[Thread2] User Input :" << input + 1 << endl;
}
catch (const std::logic_error& e) // 事件对象被关闭
{
cerr << "[Thread2] error:" << e.what() << endl;
}
});
thread t3([&] {
try
{
// 等待事件,限时10秒
if (!evt->Wait(duration_cast<milliseconds>(seconds(10))))
{
// 10秒内都没完成
cout << "[Thread3] Wait time out;" << endl;
return;
}
cout << "[Thread3] User Input :" << input + 2 << endl;
}
catch (const std::logic_error& e) // 事件对象被关闭
{
cerr << "[Thread3] error:" << e.what() << endl;
}
});
// 等待用户输入
cout << "Input an integer :";
cin >> input;
if (input < 0)
{
// 关闭事件
evt->Close();
}
else
{
// 设置事件信号
evt->Set();
}
t.join();
t2.join();
t3.join();
return 0;
}
上述代码对ManualResetEvent的使用作出了示范,用户通过调用ManualResetEvent::CreateEvent构建可自动释放的事件实例对象,而不是直接使用“new ManualResetEventImpl()”; 随后使用了三条线程演示事件的等待与限时等待,而主线程则演示了事件的信号设置。至于AutoResetEvent的示例我就懒得写了,读者可自行测试。
上述的代码和Demo在VS2017中通过测试,并分别在Windows和Linux平台上运行成功。
作为一贯使用Windows开发的C++人员,当你发现C++所提供的标准库中并没有包括Windows事件对象的高仿类时,是不是觉得很沮丧?其实无需懊恼,我们完全可以基于已有的mutex和condition_variable两个类来封装出仿Windows内核事件对象,这样一来,就算是跨平台的事件对象了。
其实在我写这篇文章时,我在工作中主要使用的语言还是C#,所以在编写C++代码时,代码和设计风格以及命名规范更多会偏向C#的方式。废话不多说,下面上代码(以下代码在VS2017中验证通过):
Event.h
仿照.NET中的层次设计,我编写了如下几个接口, 这几个接口都是纯虚的抽象类,没有任何的实现方法。#pragma once
#include <iostream>
#include <functional>
#include <memory>
#include <ratio>
#include <chrono>
using namespace std;
using namespace chrono;
namespace Threads
{
// 可等待句柄
class WaitHandle
{
public:
virtual ~WaitHandle();
// 等待事件直到为有信号状态
virtual void Wait() = 0;
// 等待事件直到为有信号状态或超时
virtual bool Wait(const milliseconds& time) = 0;
};
// 事件句柄
class EventHandle : public WaitHandle
{
public:
// 设置事件为有信号状态
virtual void Set() = 0;
// 释放事件对象
virtual void Close() = 0;
};
// 手动重置事件
class ManualResetEvent : public EventHandle
{
public:
// 创建事件对象
static unique_ptr<ManualResetEvent> CreateEvent();
public:
// 重置事件状态
virtual void Reset() = 0;
// 当前是否处于有信号状态
virtual bool IsSignaled() const = 0;
};
// 自动重置事件
class AutoResetEvent : public EventHandle
{
public:
// 创建事件对象
static unique_ptr<AutoResetEvent> CreateEvent();
};
}
EventImpl.h
这里是对上述接口的实现类定义,从设计的角度而言,用户不应该直接使用这个类,而是应该通过上述定义的CreateEvent方法中获得事件的实例对象。#pragma once
#include "Event.h"
#include <mutex>
#include <atomic>
#include <condition_variable>
namespace Threads {
// 手动重置事件
class ManualResetEventImpl : public ManualResetEvent
{
mutex m_mutex;
condition_variable m_cond;
volatile bool m_bSignal;
atomic<bool> m_bDisposed;
public:
// 初始化事件
ManualResetEventImpl();
// 设置事件为有信号状态
virtual void Set();
// 重置事件状态
virtual void Reset();
// 当前是否处于有信号状态
virtual bool IsSignaled() const;
// 等待事件直到为有信号状态
virtual void Wait();
// 等待事件直到为有信号状态或超时
virtual bool Wait(const milliseconds& time);
// 释放事件对象
virtual void Close();
private:
void CheckDisposed()const;
};
// 自动重置事件
class AutoResetEventImpl : public AutoResetEvent
{
mutex m_mutex;
condition_variable m_cond;
atomic<bool> m_bDisposed;
public:
AutoResetEventImpl();
// 设置事件为有信号状态
virtual void Set();
// 等待事件直到为有信号状态
virtual void Wait();
// 等待事件直到为有信号状态或超时
virtual bool Wait(const milliseconds& time);
// 释放事件对象
virtual void Close();
private:
void CheckDisposed()const;
};
}
Event.cpp
这里主要是提供了CreateEvent方法的实现,本质上就是构建一个实体对象。#include "Event.h"
#include "EventImpl.h"
#include <iostream>
#include <memory>
using namespace Threads;
using namespace std;
unique_ptr<ManualResetEvent> ManualResetEvent::CreateEvent()
{
unique_ptr<ManualResetEvent> ptr (new ManualResetEventImpl());
return ptr;
}
WaitHandle::~WaitHandle() = default;
unique_ptr<AutoResetEvent> AutoResetEvent::CreateEvent()
{
return unique_ptr<AutoResetEvent>(new AutoResetEventImpl());
}
EventImpl.cpp
这里是关键代码了!!代码不多,逻辑易懂,自己看↓#include "EventImpl.h"
using namespace Threads;
ManualResetEventImpl::ManualResetEventImpl()
:m_bSignal(false), m_bDisposed(false)
{
}
void ManualResetEventImpl::Set()
{
lock_guard<mutex> lock(m_mutex);
CheckDisposed();
if (!m_bSignal)
{
m_bSignal = true;
m_cond.notify_all();
}
}
void ManualResetEventImpl::Reset()
{
lock_guard<mutex> lock(m_mutex);
CheckDisposed();
m_bSignal = false;
}
bool ManualResetEventImpl::IsSignaled()const
{
CheckDisposed();
return m_bSignal;
}
void ManualResetEventImpl::Wait()
{
unique_lock<mutex> lock(m_mutex);
CheckDisposed();
m_cond.wait(lock, [this]{
return IsSignaled();
});
}
bool ManualResetEventImpl::Wait(const milliseconds & time)
{
unique_lock<mutex> lock(m_mutex);
CheckDisposed();
return m_cond.wait_for(lock, time, [this] {return IsSignaled(); });
}
void ManualResetEventImpl::Close()
{
auto expected = false;
if (!m_bDisposed.compare_exchange_weak(expected, true))
{
return;
}
lock_guard<mutex> lock(m_mutex);
m_cond.notify_all();
}
void ManualResetEventImpl::CheckDisposed()const
{
if (m_bDisposed.load() == true)
{
throw logic_error("ManualResetEventImpl Disposed.");
}
}
AutoResetEventImpl::AutoResetEventImpl()
:m_bDisposed(false)
{
}
void AutoResetEventImpl::Set() {
lock_guard<mutex> lock(m_mutex);
CheckDisposed();
m_cond.notify_all();
}
void AutoResetEventImpl::Wait()
{
{
unique_lock<mutex> lock(m_mutex);
CheckDisposed();
m_cond.wait(lock);
}
CheckDisposed();
}
bool AutoResetEventImpl::Wait(const milliseconds & time)
{
bool result;
{
unique_lock<mutex> lock(m_mutex);
CheckDisposed();
result = m_cond.wait_for(lock, time) == cv_status::no_timeout;
}
CheckDisposed();
return result;
}
void AutoResetEventImpl::Close()
{
auto expected = false;
if (!m_bDisposed.compare_exchange_weak(expected, true))
{
return;
}
lock_guard<mutex> lock(m_mutex);
m_cond.notify_all();
}
void AutoResetEventImpl::CheckDisposed() const
{
if (m_bDisposed.load() == true)
{
throw logic_error("AutoResetEventImpl Disposed.");
}
}
以上的代码提供了两种事件的定义实现,分别是手动重置事件(ManualResetEvent)和自动重置事件(AutoResetEvent),接下来的是Demo主程序的代码,演示如何使用ManualResetEvent。
main.cpp#include <cstdio>
#include <stdlib.h>
#include <iostream>
#include <memory>
#include "Event.h"
#include <thread>
using namespace std;
using namespace Threads;
using namespace chrono;
int main()
{
auto evt = ManualResetEvent::CreateEvent();
int input = 0;
thread t([&] {
try
{
// 等待事件
evt->Wait();
cout << "[Thread1] User Input :" << input << endl;
}
catch (const std::logic_error& e) // 事件对象被关闭
{
cerr << "[Thread1] error:" << e.what() << endl;
}
});
thread t2([&] {
try
{
// 等待事件,限时5秒
if (!evt->Wait(duration_cast<milliseconds>(seconds(5))))
{
// 5秒内都没完成
cout << "[Thread2] Wait time out;" << endl;
return;
}
cout << "[Thread2] User Input :" << input + 1 << endl;
}
catch (const std::logic_error& e) // 事件对象被关闭
{
cerr << "[Thread2] error:" << e.what() << endl;
}
});
thread t3([&] {
try
{
// 等待事件,限时10秒
if (!evt->Wait(duration_cast<milliseconds>(seconds(10))))
{
// 10秒内都没完成
cout << "[Thread3] Wait time out;" << endl;
return;
}
cout << "[Thread3] User Input :" << input + 2 << endl;
}
catch (const std::logic_error& e) // 事件对象被关闭
{
cerr << "[Thread3] error:" << e.what() << endl;
}
});
// 等待用户输入
cout << "Input an integer :";
cin >> input;
if (input < 0)
{
// 关闭事件
evt->Close();
}
else
{
// 设置事件信号
evt->Set();
}
t.join();
t2.join();
t3.join();
return 0;
}
上述代码对ManualResetEvent的使用作出了示范,用户通过调用ManualResetEvent::CreateEvent构建可自动释放的事件实例对象,而不是直接使用“new ManualResetEventImpl()”; 随后使用了三条线程演示事件的等待与限时等待,而主线程则演示了事件的信号设置。至于AutoResetEvent的示例我就懒得写了,读者可自行测试。
上述的代码和Demo在VS2017中通过测试,并分别在Windows和Linux平台上运行成功。
相关文章推荐
- C++并行编程(二): 利用C++标准库实现Semaphore信号量
- Windows Via C/C++:内核模式下的线程同步——事件内核对象
- 《Windows via C/C++》学习笔记 —— 内核对象的“线程同步”之“事件内核对象”
- windows下多线程同步(利用事件对象,互斥对象,关键代码段)实现
- windows系统调用 利用事件对象实现进程通信
- 利用C++的operator new实现同一对象多次调用构造函数
- 《Windows via C/C++》学习笔记 —— 内核对象的“线程同步”之“等待定时器”
- 内核对象 Windows via C/C++ (三)
- windows笔记-【内核对象线程同步】事件内核对象
- Windows内核对象 - 通过异步程序调用(APC)实现的定时功能
- Windows Via C/C++ Part Ⅰ Chapter3: 内核对象(3)
- Windows编程--线程和内核对象的同步-事件内核对象
- 用C++实现Win32事件对象,同步线程
- Linux平台用C++实现事件对象,同步线程
- 利用事件对象实现线程同步
- Windows Via C/C++ Part Ⅰ Chapter3: 内核对象(1)
- 《Windows via C/C++》学习笔记 —— 内核对象的“线程同步”之“互斥内核对象”
- 利用事件对象实现线程同步
- windows下内核对象的跨边界共享实现一
- windows笔记-【内核对象线程同步】事件内核对象