【发布时间】:2012-02-03 02:19:27
【问题描述】:
我正在使用 Visual Studio 2008 在 Windows XP Pro SP 3 下使用标准(非托管)C++ 进行开发。
我已经围绕 std::cout 创建了一个线程安全的包装器。这个包装器对象是一个插入式替换(即相同的名称),用于替换 #defined 到 cout 的宏。 很多代码使用它。它的行为可能与您预期的差不多:
在构造时,它会创建一个临界区。
在调用operator
在销毁时,它会销毁临界区。
这个包装器存在于静态存储中(它是全局的)。与所有此类对象一样,它在 main() 启动之前构造,并在 main() 退出后销毁。
在同样存在于静态存储中的另一个对象的析构函数中使用我的包装器是有问题的。由于此类对象的构造/销毁顺序是不确定的,我很可能会尝试锁定已被销毁的临界区。我看到的症状是我的程序阻止了锁定尝试(尽管我认为任何事情都可能发生)。
至于解决这个问题的方法......
我在析构函数中什么也做不了;具体来说,我会让关键部分继续存在。 C++ 标准保证 cout 在程序执行期间永远不会死掉,这将是使我的包装器行为相似的最佳尝试。当然,我的包装器在其空的析构函数运行后会“正式”死亡,但它可能(我讨厌这个词)会像它的析构函数运行之前一样有效。在我的平台上,情况似乎确实如此。但是,天哪,这丑陋的、不可携带的,而且以后容易损坏……
我将关键部分(但不是对 cout 的流引用)保存在 pimpl 中。所有通过 pimpl 访问临界区之前都会检查 pimpl 的非空性。碰巧我在析构函数中调用 delete 后忘记将 pimpl 设置为 0。如果我将其设置为 0(无论如何我都应该这样做),在它被破坏后调用我的包装器不会对关键部分做任何事情,但仍会将要打印的数据传递给 cout。在我的平台上,这似乎也有效。再一次,丑陋...
我可以告诉我的队友在 main() 退出后不要使用我的包装器。不幸的是,它的空气动力学性能与坦克差不多。
问题:
* 问题 1 * 对于案例1,如果我不破坏关键部分,操作系统中的关键部分就会发生资源泄漏。在我的程序完全退出后,这种泄漏是否会持续存在?如果没有,案例 1 变得更可行。
* 问题 2 * 对于案例 1 和 2,是否有人知道 在我的特定平台上在空析构函数运行后我是否确实可以安全地继续使用我的包装器?看来我可以,但我想看看是否有人知道我的平台在这种情况下的行为方式......
* 问题 3 * 我的建议显然不完美,但我没有看到真正正确的解决方案。有没有人知道这个问题的正确解决方案?
旁注:当然,如果我尝试在另一个也存在于静态存储中的对象的构造函数中使用包装器,则可能会出现相反的问题。在这种情况下,我可能会尝试锁定尚未创建的临界区。我想使用“第一次使用时构造”习语来解决这个问题,但这需要对使用我的包装器的所有代码进行语法更改。这将需要放弃使用
* 问题 4 * 正如我所说,我的包装器存在于静态存储中(它是全局的)并且它有一个 pimpl(荷尔蒙问题:))。我的印象是静态存储中变量的原始字节在加载时设置为 0(除非在代码中以不同方式初始化)。这意味着我的包装器的 pimpl 在构建包装器之前的值为 0。这是正确的吗?
谢谢你, 戴夫
【问题讨论】:
-
你不是第一个想到这个问题的。 iostream 管道中已经内置了一个低级锁。添加自己的没有意义。
-
我没有读完所有这些,但听起来this 可能对你有用。基本上,你可以搞乱静态初始化顺序。
-
当然,C++ 标准并不保证 cout 是线程安全的,但听起来您(Hans)的意思是说 Visual C++ 的 C++ 特定实现确实使 cout 线程安全.我理解正确吗?