有时,在程序中需要保存函数的上下文信息,比如某一个函数是否在执行,或者函数的调用链是怎样的。
本文尝试着解决这类问题。
问题的提出
首先,假定我们有一个线程安全的队列类:
1 | // Thread safe queue (Multi-producer/Multi-consumer) |
然后我们以此构建了一个异步任务队列:
1 | class WorkQueue { |
现在,我们有一个函数,它的执行流程取决于当前线程是否正在执行isInRun():
1 | // Some global work queue |
问题来了,我们应该如何实现WorkQueue::isInRun()方法呢?
朴素办法
也许我们可以通过一个成员变量来告诉我们是否正在执行WorkQueue::isInRun()方法,就像这样:
1 | class WorkQueue { |
为什么不用一个bool类型的m_running呢?主要是为了判断递归(recursion)。
为什么run会出现递归的情况呢?因为它内部调用了用户提交的各类任务(handlers),这些任务的实现(implement)中可能调用任何代码,其中就包括run。
另外值得强调的是,在调用这种未知的代码时,程序内部一定不能持有上锁了的互斥量,否则很容易造成死锁(deadlocks)。
这种朴素的办法可行吗?答案是否定的。
示例:
1 | // This works correctly |
更适用的办法
为了解决上述的问题,我们试图通过记录每个线程的函数调用栈来判断某一个函数是否正在执行。
具体来说,我们会在函数调用中逐层放置标记,每一个标记会连接到前一层调用的标记。如果我们将标记设为thread local,那我们可以为每一个线程创建一个可以迭代的调用标记。

参考boost::asio::detail::call_stack的实现如下:
1 | template <typename Key, typename Value = unsigned char> |
这种模板类的实现意味着下面这些使用均可行:
1 | Callstack<Foo> |
这种实现的优点如下:
- 类型安全。你可以指定任意的键值Key/Value类型。
- 每一种键值类型均代表了独立的链表。实际使用中有选择地具现化模板可以在不损失性能的情况下自由地使用。
- 将调用栈的逻辑和具体的类型解耦。换句话说,不再需要之前列出的WorkQueue::isInRun方法了。
- 其他功能。这个辅助类不仅适用于函数调用栈的检查,还可以用于调试。举例来说,通过给标记附加调试信息,当程序检测到错误时,它可以遍历标记然后打印所有的调用和调试信息。
使用示例
先来看一下如何解决上述提出的WorkQueue的问题:
1 | struct Foo { |
再看一下如何用它来打印调试信息:
1 | struct DebugInfo { |
调试信息不仅可以是函数的调用栈(LINE/FUNCTION),你还可以添加其他的附加信息,方便Debug。
在下一篇文章中,我们会使用这个Callstack类来实现我们自己的boost::asio::io_service::strand。