10 July 2023

C++ 核心指南目录

“Do not hold locks or other synchronization primitives across suspension points”

注意,此处 primitive 在编程语言中是指:某个函数、操作符、或其他类型,内置在编程语言或操作系统中,为了满足程序的快速执行。并且,这些东西是无法通过开发语言单独实现的。primitive 一般有逻辑和算术运算符,加减乘除,或通过一组机器语言指令实现的功能,比如互斥锁、信号量之类。

A function, operator, or type which is built into a programming language (or operating system), either for speed of execution or because it would be impossible to write it in the language. Primitives typically include the arithmetic and logical operations (plus, minus, and, or, etc.) and are implemented by a small number of machine language instructions.

所以,本文按照惯例,将 primitive 翻译为原语.

理由

不然会导致很危险的死锁。某些类型的等待操作会允许当前线程执行一些额外的工作,直到异步操作完成。如果这个线程保持某个锁,然后又执行一些操作,需要获取这个相同的锁,这就会导致死锁。因为该线程早已锁住这个锁。

如果协程在别的线程中获取这个锁的话,就会导致未定义的执行结果。就算我们设定显式的返回到当前线程,也会因为抛出异常,而导致协程无法继续执行,这时候,这个获取的锁就没有销毁释放。

坏例子

std::mutex g_lock;

std::future<void> Class::do_something()
{
    std::lock_guard<std::mutex> guard(g_lock);
    co_await something(); // DANGER: coroutine has suspended execution
                          // while holding a lock
    co_await somethingElse();
}

好例子

std::mutex g_lock;

std::future<void> Class::do_something()
{
    {
        std::lock_guard<std::mutex> guard(g_lock);
        // modify data protected by lock
    }
    co_await something(); // OK: lock has been released before
                          // coroutine suspends
    co_await somethingElse();
}

注意

这个模式的性能也不好。当程序执行到悬挂点 co_await 时,当前函数就停止执行,其他代码开始运行。可能这个协程会悬挂很长时间。这样,整个悬挂时间中,锁都是被锁着的,其他线程如果需要这个锁的话,就无法执行。

强化

标记所有在协程悬挂点未释放的锁。