C++ 带有条件和无锁通知的便携式事件检查器
我需要让一个实时(音频)线程在事件发生时通知另一个后台线程。后台“checker”线程将执行一些昂贵的操作 因此,我的限制是:C++ 带有条件和无锁通知的便携式事件检查器,c++,events,lock-free,condition-variable,C++,Events,Lock Free,Condition Variable,我需要让一个实时(音频)线程在事件发生时通知另一个后台线程。后台“checker”线程将执行一些昂贵的操作 因此,我的限制是: 通知操作必须是无锁的(在等待端没有此类限制) 防止检查器线程浪费CPU时间,并在不需要时将其正确休眠,因为事件之间的间隔可能长达分钟 代码必须是可移植的(osx、windows、android、ios) 我提出了一个使用条件变量和条件标志的简单解决方案。当条件变量在检查器线程开始等待之前发出信号时,需要该标志来防止检查器线程等待(可能永远等待) struct Eve
- 通知操作必须是无锁的(在等待端没有此类限制)
- 防止检查器线程浪费CPU时间,并在不需要时将其正确休眠,因为事件之间的间隔可能长达分钟
- 代码必须是可移植的(osx、windows、android、ios)
struct EventChecker {
std::mutex mtx;
std::condition_variable cv;
std::atomic<bool> flag;
bool alive;
std::thread checkerThread;
EventChecker() {
alive = true;
checkerThread = std::thread([this]() {
while (alive) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this]() { return flag.exchange(false); });
std::cout << " action! ";
}
});
}
void notify() {
flag.store(true);
cv.notify_one();
}
~EventChecker() {
alive = false;
notify();
checkerThread.join();
}
};
通过这个简单的测试,很容易出现死锁(在macbook上复制):
我错过什么了吗?我是否误用了std::condition_variable
,还有其他更适合我的用途吗?对于原子检查和无锁等待,您可以使用futex机制。不幸的是,此机制不是任何C/C++标准的一部分,它是低级操作系统支持的一部分:
- Linux(还有Android)也有syscall
- Windows具有多种功能
我还没有在OSX上找到futex的任何类似物,也不知道它在ios上的存在。我实施了一些可能的解决方案,需要根据情况接受一些可能可以接受的妥协,并在周末做了一些测量,因为我喜欢玩 我重写了
EventChecker
,以处理事件
类的三种可能实现:
:使用Event\u with stdutex
std::mutex的参考“规范”实现
:它仍然使用互斥锁保护状态,但使用的是轻量级互斥锁(确切地说是旋转锁)。这里的想法是,当事件发生相对较少时,检查线程几乎总是在通知发生时等待,因此通知线程获取锁几乎没有开销Event_with lightlock
:通知不受锁保护,但我们使用Event\u WithTimeout
让检查线程在最坏的情况下唤醒(如问题中所述)。当我们需要不惜一切代价快速发送通知,并且在最坏的情况下,我们能够以与wait\u for
超时相等的延迟来处理事件时,可以使用此方法。超时时间的选择是在最坏情况下的响应能力和CPU时间节省之间进行权衡wait_for
struct Event_WithStdMutex {
std::condition_variable_any cv;
std::atomic<bool> condition;
std::mutex mtx;
Event_WithStdMutex() : condition(false) {}
void wait() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this]() { return condition.exchange(false); });
}
void notify() {
std::unique_lock<std::mutex> lock(mtx);
condition.store(true);
cv.notify_one();
}
};
struct LightMutex {
std::atomic_flag flag = ATOMIC_FLAG_INIT;
void lock() { while (flag.test_and_set(std::memory_order_acquire)); }
void unlock() { flag.clear(std::memory_order_release); }
};
struct Event_WithLightLock {
std::condition_variable_any cv;
std::atomic<bool> condition;
LightMutex mtx;
Event_WithLightLock() : condition(false) {}
void wait() {
std::unique_lock<LightMutex> lock(mtx);
cv.wait(lock, [this]() { return condition.exchange(false); });
}
void notify() {
std::unique_lock<LightMutex> lock(mtx);
condition.store(true);
cv.notify_one();
}
};
struct Event_WithTimeout {
std::condition_variable cv;
std::atomic<bool> condition;
std::mutex mtx;
std::chrono::milliseconds timeout;
Event_WithTimeout() : condition(false), timeout(10) {}
void wait() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait_for(lock, timeout, [this]() { return condition.exchange(false); });
}
void notify() {
condition.store(true);
cv.notify_one();
}
};
以及对std::condition_variable
的notify_one()
在无人等待的情况下所用时间的测量:
void measureNotifyConditionVariable()
{
std::condition_variable cv;
auto begin = std::chrono::high_resolution_clock::now();
for (int i = 0; i < N; i++){
cv.notify_one();
}
auto dur = std::chrono::high_resolution_clock::now() - begin;
std::cout << "std::condition_variable avg notify time (no-one waiting): "
<< std::chrono::duration<double, std::nano>(dur).count() / N << " ns \n";
}
void measureNotifyConditionVariable()
{
std::条件变量cv;
自动开始=标准::时钟::高分辨率时钟::现在();
对于(int i=0;i std::cout“修复”这个问题的一种方法是允许checker线程定期使用wait_for(…)
。如果我没有找到更好的解决方案,那么这可能是一个很好的折衷方案,我决定可以接受事件处理过程中的几毫秒延迟。为什么不在有工作要做的时候启动一个新线程呢?如果你只是偶尔有一些工作,并且线程无事可做的风险很高,我认为这就是b更好的选择。启动线程是一个昂贵的操作,可能会在堆上分配内存,这是在实时音频线程上要避免的。我目前正在查看信号量,仔细阅读,希望能从那里使用一些东西!那么,也许,忙碌的等待真的是唯一的选择。有趣的是,我需要找到一些r mac os和ios。显然Futex不是posix的一部分,是吗?不,posix不包含Futex。
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this]() { return flag.exchange(false); });
// NB: wait() atomically releases lock and blocks the thread
mtx.unlock();
struct Event_WithStdMutex {
std::condition_variable_any cv;
std::atomic<bool> condition;
std::mutex mtx;
Event_WithStdMutex() : condition(false) {}
void wait() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this]() { return condition.exchange(false); });
}
void notify() {
std::unique_lock<std::mutex> lock(mtx);
condition.store(true);
cv.notify_one();
}
};
struct LightMutex {
std::atomic_flag flag = ATOMIC_FLAG_INIT;
void lock() { while (flag.test_and_set(std::memory_order_acquire)); }
void unlock() { flag.clear(std::memory_order_release); }
};
struct Event_WithLightLock {
std::condition_variable_any cv;
std::atomic<bool> condition;
LightMutex mtx;
Event_WithLightLock() : condition(false) {}
void wait() {
std::unique_lock<LightMutex> lock(mtx);
cv.wait(lock, [this]() { return condition.exchange(false); });
}
void notify() {
std::unique_lock<LightMutex> lock(mtx);
condition.store(true);
cv.notify_one();
}
};
struct Event_WithTimeout {
std::condition_variable cv;
std::atomic<bool> condition;
std::mutex mtx;
std::chrono::milliseconds timeout;
Event_WithTimeout() : condition(false), timeout(10) {}
void wait() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait_for(lock, timeout, [this]() { return condition.exchange(false); });
}
void notify() {
condition.store(true);
cv.notify_one();
}
};
template <typename Event> struct EventChecker {
bool alive;
std::thread checkerThread;
Event event;
EventChecker() {
alive = true;
checkerThread = std::thread([this]() {
while (alive) {
event.wait();
std::this_thread::sleep_for(std::chrono::microseconds(1)); // comment this for more fun
}
});
}
void notify() {
event.notify();
}
~EventChecker() {
alive = false;
notify();
checkerThread.join();
}
};
const int N = 1000000;
template <typename Event> void measureNotify(std::string eventType) {
EventChecker<Event> evChecker;
auto begin = std::chrono::high_resolution_clock::now();
for (int i = 0; i < N; i++){
evChecker.notify();
}
auto dur = std::chrono::high_resolution_clock::now() - begin;
std::cout << "EventChecker (with " << eventType << ") avg notify time: "
<< std::chrono::duration<double, std::nano>(dur).count() / N << " ns \n";
Event ev;
begin = std::chrono::high_resolution_clock::now();
for (int i = 0; i < N; i++) {
ev.notify();
}
dur = std::chrono::high_resolution_clock::now() - begin;
std::cout << eventType << " avg notify time (no-one waiting): "
<< std::chrono::duration<double, std::nano>(dur).count() / N << " ns \n\n";
}
void measureNotifyConditionVariable()
{
std::condition_variable cv;
auto begin = std::chrono::high_resolution_clock::now();
for (int i = 0; i < N; i++){
cv.notify_one();
}
auto dur = std::chrono::high_resolution_clock::now() - begin;
std::cout << "std::condition_variable avg notify time (no-one waiting): "
<< std::chrono::duration<double, std::nano>(dur).count() / N << " ns \n";
}