【问题标题】:Why sleep_for calls FreeLibrary?为什么 sleep_for 调用 FreeLibrary?
【发布时间】:2015-08-26 15:23:34
【问题描述】:

追踪这个简单的代码让我很震惊:

#include <thread>

void foo()
{
    for (int i = 0; i < 1000000; ++i) {
        std::this_thread::sleep_for(std::chrono::nanoseconds(1));
    }
}

int main()
{
    std::thread t(foo);
    t.join();
}

你猜怎么着? sleep_for 每次都调用 FreeLibrary!

kernel32.dll!_FreeLibraryStub@4()
msvcr120d.dll!Concurrency::details::DeleteAsyncTimerAndUnloadLibrary(_TP_TIMER * timer) Line 707
msvcr120d.dll!Concurrency::details::_Timer::_Stop() Line 111
msvcr120d.dll!Concurrency::details::_Timer::~_Timer() Line 100
msvcr120d.dll!`Concurrency::wait'::`7'::TimerObj::~TimerObj()
msvcr120d.dll!Concurrency::wait(unsigned int milliseconds) Line 155
test826.exe!std::this_thread::sleep_until(const xtime * _Abs_time) Line 137
test826.exe!std::this_thread::sleep_for<__int64,std::ratio<1,1000000000> >(const std::chrono::duration<__int64,std::ratio<1,1000000000> > & _Rel_time) Line 162
test826.exe!foo() Line 6

为什么 sleep_for 必须调用 FreeLibrary ?

该程序使用 boost 库需要 2 秒,使用 msvcrt(发布模式)需要 3 分钟以上(失去耐心)。我无法想象。

【问题讨论】:

  • 我不明白您如何期望睡眠 1 纳秒 起作用。
  • 这是一个非常特定于实现的东西,只有 Visual C++ 标准库开发人员才能知道答案。
  • @JoachimPileborg:幸运的是,其中一些开发人员经常这样做 :)
  • 睡眠可以预期释放当前时间片。因此(假设 10ms 时间片)您希望程序休眠 2.7 小时。
  • 幸运或不幸... :-) 我们还发布了几乎所有的运行时库源代码,因此单步执行运行时库代码以查看发生了什么通常非常简单。在这种情况下,Concurrency::wait_Timer 类的 ConcRT 代码有许多有用的 cmets。

标签: c++ visual-c++ msvc12


【解决方案1】:

在 Visual C++ 2013 中,大部分 C++ 标准库并发功能位于 Concurrency Runtime (ConcRT) 之上。 ConcRT 是一个工作窃取运行时,提供协作调度和阻塞。

这里,Concurrency::wait 使用线程池计时器来执行等待。它使用LoadLibrary/FreeLibrary 在计时器挂起期间增加托管 ConcRT 运行时的模块的引用计数。这样可以确保在等待期间模块不会被卸载。

我不是 ConcRT 专家(甚至不是专家),所以我不能 100% 确定可以在此处卸载 ConcRT 模块的确切情况。我知道我们对std::thread_beginthreadex 进行了类似的更改,以获取对托管线程回调的模块的引用,以确保在线程执行时不会卸载模块。

在 Visual C++ 2015 中,C++ 标准库并发功能被修改为直接位于 Windows 操作系统原语(例如CreateThreadSleep 等)之上,而不是 ConcRT。这样做是为了提高性能,解决混合使用 C++ 线程功能与使用操作系统功能时的正确性问题,以及作为更普遍地去强调 ConcRT 的一部分。

请注意,在 Windows 上,睡眠精度以毫秒为单位,零毫秒的睡眠通常意味着“在返回给我之前先去做其他有用的工作”。如果您使用 Visual C++ 2015 编译您的程序,每次调用 wait_for 都会依次调用 Sleep(0),这“导致线程将其剩余时间片让给任何其他准备运行的线程。”

【讨论】:

  • 顺便说一句,“确保模块没有被卸载”,你为什么不把这个留给开发人员呢?卸载正在运行的模块 - 这没有任何意义。
  • 考虑以下情况:[1] 模块通过 COM 加载,[2] 模块内部的某些 COM 对象通过 std::async 或 std::thread 启动一些异步操作,然后 [ 3] 所有 COM 对象都不存在了,但异步操作仍处于挂起状态或后台线程仍在运行。此时 COM 运行时可能会释放其对模块的引用。如果异步操作或工作线程不拥有自己对模块的引用,则引用计数将降至零,加载器将从它们下面卸载模块。
  • 出于几个原因,我们不希望将此留给开发人员。首先,在哪里实际上 需要获取引用并不明显。我们不希望开发人员每次启动线程或任务时都必须获取引用——如果没有别的,这将需要到处都有特定于平台的代码,即使在应该跨 C++ 实现和可移植的代码中也是如此。操作系统。此外,Windows 应用商店应用无法增加通过 COM 运行时加载的模块的引用计数(所需的 API 不可调用)。
  • @JamesMcNellis 所以这就是原因。我不了解 Windows 应用商店应用程序,但如果 COM 对象不存在,为什么不进行同步工作以确保所有异步操作都已完成或取消?如果不是,则说明虽然一个对象完成了,但是一些相关的代码还在运行,这不是RAII。
  • 在实践中,明确地等待所有异步操作完成并不容易,尤其是在高度异步的 API 中,如新的 Windows 运行时 API(非常容易丢失任务跟踪)。几乎没有应用程序 100% 正确地做到这一点。
猜你喜欢
  • 2010-12-26
  • 2011-09-03
  • 1970-01-01
  • 2018-09-07
  • 2020-09-18
  • 1970-01-01
  • 2012-12-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多