您的位置:首页 > 编程语言 > C语言/C++

C++并行编程(一): 利用C++标准库实现仿Windows内核事件对象

2018-02-27 11:46 681 查看
    我们知道,无论是Linux还是Windows,在底层的操作系统API中都提供了一些与线程间同步操作相关的内核对象,就以我最熟悉的Windows讲起,就提供了诸如临界区(关键段)、事件、互斥变量、条件变量、信号量、读写锁、旋转锁甚至原子操作等等的一系列方式,而Linux中也提供了类似的机制。由于这些底层机制在应用程序开发中经常使用,但在不同操作系统平台中调用的API又不一样,所有可见市面上有些C/C++开源项目对系统底层调用作了跨平台封装处理。所幸,C++官方也不甘落后,从C++11开始,在其标准库中也提供了系统无关的线程同步机制,其中主要是mutexcondition_variable两个类。
    作为一贯使用Windows开发的C++人员,当你发现C++所提供的标准库中并没有包括Windows事件对象的高仿类时,是不是觉得很沮丧?其实无需懊恼,我们完全可以基于已有的mutexcondition_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 Windows Linux 事件