【问题标题】:Using GetLastError with threads将 GetLastError 与线程一起使用
【发布时间】:2015-10-30 11:07:44
【问题描述】:

不久前,我为 Windows API 函数编写了一个简单的包装类。我编写了一组单元测试来验证该类产生的结果与对 API 的直接调用匹配。

最近我回过头来添加有关跨不同线程使用包装类的单元测试。我发现::GetLastError 函数存在一些问题。根据MSDN,该函数应保留每个线程的最后一个错误代码:

检索调用线程的最后一个错误代码值。最后一个错误代码是在每个线程的基础上维护的。多个线程不会覆盖彼此的最后一个错误代码。

我发现在某些情况下,最后一个错误代码实际上变为零。我已经设法用下面的简单程序在单元测试之外复制了这个问题:

#include "stdafx.h"

#include <condition_variable>
#include <mutex>
#include <thread>
#include <Windows.h>

int main(int argc, char* argv[])
{
    ::DWORD setError1 = 123;
    ::DWORD setError2 = 456;

    // scenario 1 - show that main thread not polluted by sub-thread
    const auto act1 = [](::DWORD errorNo)
    {
        ::SetLastError(errorNo);
        const auto a = ::GetLastError(); // a = 123
    };

    ::SetLastError(setError2);
    const auto b = ::GetLastError(); // b = 456
    const auto c = ::GetLastError(); // c = 456
    std::thread sub1(act1, setError1);
    sub1.join();
    const auto d = ::GetLastError(); // d = 0 - WHY???


    // scenario 2 - show that sub thread not polluted by main thread
    std::condition_variable conditional;
    std::mutex mutex;
    bool flag = false;
    ::DWORD e;
    const auto act2 = [&mutex, &flag, &e, &conditional](::DWORD errorNo)
    {
        std::unique_lock<std::mutex> lock(mutex);
        ::SetLastError(errorNo);
        conditional.wait(lock, [&flag] { return flag; });
        e = ::GetLastError();  // e = 456 in Windows 8.1, 0 in Windows 10.0.10240.0 - WHY???
    };

    std::thread sub2(act2, setError2);
    {
        std::lock_guard<std::mutex> guard(mutex);
        ::SetLastError(setError1);
        flag = true;
    }
    conditional.notify_all();
    sub2.join();
    const auto f = ::GetLastError();  // f = 123;
    return 0;
}

我遇到的问题是de

  • d - 我发现 主线程 的最后一个错误代码在使用子线程时重置为零。

  • e - 使用 Windows 10 SDK 时,我发现 子线程 在等待 std::condition_variable 时看到最后一个错误重置为零。使用 Windows 8.1 SDK 时不会重置。

有人可以帮助解释我看到的结果吗?这是 Windows API 中的错误,还是微软 C++ 实现中的错误?还是我自己的代码中的错误?

【问题讨论】:

    标签: c++ multithreading winapi visual-c++ visual-studio-2015


    【解决方案1】:

    这很简单。以下是发生的事情:

    1. 你打电话给SetLastError
    2. 您调用另一个函数,该函数又调用SetLastError
    3. 您调用GetLastError 并获取最新值,这是在第 2 项中设置的值,而不是在第 1 项中设置的值。

    所以,看看你的代码,我的注释:

    ::SetLastError(setError2);          // item 1 from the list above
    std::thread sub1(act1, setError1);  // item 2
    sub1.join();                        // item 2
    const auto d = ::GetLastError();    // item 3
    

    还有你的另一个例子:

     ::SetLastError(errorNo);                          // item 1
     conditional.wait(lock, [&flag] { return flag; }); // item 2
     e = ::GetLastError();                             // item 3
    

    在每种情况下,我已注释为 item 2 的代码正在调用 API 函数,而这些函数又调用 SetLastError

    【讨论】:

    • 那么微软标准库的实现,或者至少它的线程实现,使用了Windows API?
    • 当然。不通过 Windows API 就无法创建线程。对于同步对象也是如此。这些将在可能的情况下在系统对象之上实现。
    • ^^ 大卫所说的。 I/O、线程、线程间同步等必须由操作系统管理,否则会出现混乱。如果您通过库显式或间接进行 API 调用,但它失败了,您应该在下一步调用 GetLastError,否则您可能会将错误信息丢失到另一个成功的 API。
    • @CamelCase:Visual Studio 附带(几乎)完整的 CRT 和 C++ 标准库源代码。
    • @MartinJames - 是的,我们必须得到最后一个错误的任何调用都是在 API 调用之后立即进行的。这纯粹是从对我们的函数包装器进行单元测试时提出的。我不知道错误代码在使用 std 类/函数时也会更新。此答案的链接当前嵌入到我的代码 cmets 中(特别是为什么我希望在某些点返回零)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-06
    • 2018-07-29
    • 2011-12-13
    • 2021-09-14
    • 2011-08-29
    • 1970-01-01
    相关资源
    最近更新 更多