C++ 带有条件和无锁通知的便携式事件检查器

C++ 带有条件和无锁通知的便携式事件检查器,c++,events,lock-free,condition-variable,C++,Events,Lock Free,Condition Variable,我需要让一个实时(音频)线程在事件发生时通知另一个后台线程。后台“checker”线程将执行一些昂贵的操作 因此,我的限制是: 通知操作必须是无锁的(在等待端没有此类限制) 防止检查器线程浪费CPU时间,并在不需要时将其正确休眠,因为事件之间的间隔可能长达分钟 代码必须是可移植的(osx、windows、android、ios) 我提出了一个使用条件变量和条件标志的简单解决方案。当条件变量在检查器线程开始等待之前发出信号时,需要该标志来防止检查器线程等待(可能永远等待) struct Eve

我需要让一个实时(音频)线程在事件发生时通知另一个后台线程。后台“checker”线程将执行一些昂贵的操作

因此,我的限制是:

  • 通知操作必须是无锁的(在等待端没有此类限制)

  • 防止检查器线程浪费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
    让检查线程在最坏的情况下唤醒(如问题中所述)。当我们需要不惜一切代价快速发送通知,并且在最坏的情况下,我们能够以与
    wait_for
    超时相等的延迟来处理事件时,可以使用此方法。超时时间的选择是在最坏情况下的响应能力和CPU时间节省之间进行权衡
以下是实现:

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;istd::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";
}