在Windows中等待std :: atomic <int>的正确方法?

M.M*_*M.M 3 c++ windows multithreading atomic

以下代码有效,但它有一个问题:

#include <atomic>
#include "windows.h"

std::atomic<int> foo;

DWORD WINAPI baz(void *) { Sleep(10000); foo.store(1); return 0;}

int main()
{
    foo.store(0);
    HANDLE h = CreateThread(NULL, 0, baz, NULL, 0, NULL);

    while ( !foo.load() )
    {
        Sleep(0);
    }
    WaitForSingleObject(h, INFINITE);
    CloseHandle(h);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

程序在等待时使用最大CPU.

如果我Sleep(0);改为Sleep(1);那么它使用0%的CPU,但我担心一些事情:

  • 这会在我的程序中引入不必要的延迟:如果在轮询之间设置了标志,则会浪费几微秒
  • 这可能仍然消耗比必要更多的系统资源,以便唤醒并调用load()每毫秒.

有没有更好的办法?

背景:我有一些代码正在使用Win32事件唤醒线程,使用WaitForMultipleObjects,但我想知道我是否可以使用std::atomic标志,目的是使代码更简单,更快速和/或更便携.IDK操作系统如何实现WaitForSingleObject和WaitForMultipleObjects,例如它是否Sleep(1)在内部使用或者是否有更智能的技术可用.

注意:atomic<int>是无锁的; 生成的循环程序集是:

    movq    __imp_Sleep(%rip), %rbx
    movq    %rax, %rsi
    jmp .L4
    .p2align 4,,10
.L5:
    xorl    %ecx, %ecx
    call    *%rbx
.L4:
    movl    foo(%rip), %edx
    testl   %edx, %edx
    je  .L5
Run Code Online (Sandbox Code Playgroud)

Mik*_*ine 9

你不应该等待std::atomic,他们不是为此而设计的.如果你想要一个非忙碌的等待,那么你想要一个std::condition_variable.

A std::condition_variable专门设计为能够等到它发出信号而不使用任何CPU并立即唤醒.

它们的用法有点冗长,你需要将它们与互斥量结合起来,但是一旦你习惯了它们,它们就会很强大:

#include <condition_variable>
#include <mutex>
#include <thread>

std::condition_variable cv;
std::mutex lock;
int foo;

void baz()
{
    std::this_thread::sleep_for(std::chrono::seconds(10));

    {
        auto ul = std::unique_lock<std::mutex>(lock);
        foo = 1;
    }
    cv.notify_one();
}

int main()
{
    foo = 0;

    auto thread = std::thread(baz);

    {
        auto ul = std::unique_lock<std::mutex>(lock);
        cv.wait(ul, [](){return foo != 0;});
    }

    thread.join();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)


Jan*_*rný 9

从 C++20 开始,您可以使用std::atomic<T>::wait(T old). 它的工作原理与条件变量类似,并且不存在自旋锁。

要使用它,您可以使用原子的旧(当前)值调用此方法,并且它将阻塞,直到原子的值更改为不同的值。(请注意,ABA 问题仍然存在——可能会错过对其他值的一些简短更改。)

当值发生变化时,其他线程可以通过调用std::atomic<T>::notify_one()或来告知这一点std::atomic<T>::notify_all()。因此你的例子是:

#include <atomic>
#include "windows.h"

std::atomic<int> foo;

DWORD WINAPI baz(void *) { Sleep(10000); foo.store(1); foo.notify_one(); return 0;}

int main()
{
    foo.store(0);
    HANDLE h = CreateThread(NULL, 0, baz, NULL, 0, NULL);

    foo.wait(0);
    WaitForSingleObject(h, INFINITE);
    CloseHandle(h);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)


Bar*_*rry 7

如果你想要的是完成信号,那么使用标准库你有两个解决方案 - a condition_variable和a future.由于Mike Vine已经使用前者提供了解决方案,我将展示如何使用后者:

#include <future>
#include <thread>
#include <iostream>

void baz(std::promise<int>& pr)
{
    std::this_thread::sleep_for(std::chrono::seconds(10));
    pr.set_value(1); // fulfill the prmoise
}

int main()
{
    std::promise<int> promise;
    auto future = promise.get_future();

    auto thread = std::thread(baz, std::ref(promise));
    int foo = future.get(); // blocks until promise is fulfilled
    std::cout << "Thread yielded: " << foo << std::endl;
    thread.join();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

请注意,a condition_variable可以多次通知和等待,但a promise只能满足一次.