简体   繁体   English

如何使用 std::jthread 安全地注册 std::stop_callback

[英]how to safely register std::stop_callback with std::jthread

I'm a bit confused about how to safely register callbacks for jthread .我对如何安全地为jthread注册回调有点困惑。 You need the token so that means that you would need to do the registration after creating the jthread which means that the callback would be destroyed before the jthread .您需要token ,这意味着您需要在创建jthread之后进行注册,这意味着回调将在jthread之前被销毁。 In the example below cb5 and cb6 obviously get destroyed before the dtor of the jthread starts so they automatically deregister themselves and never execute.在下面的示例中, cb5cb6显然在 jthread 的jthread启动之前被销毁,因此它们会自动注销自己并且永远不会执行。 Conversely, cb1 and cb2 are explicitly destroyed after the destruction of jthread so they are guaranteed to execute as a side effect to the dtor requesting a stop.相反, cb1cb2jthread销毁后被显式销毁,因此可以保证它们作为 dtor 请求停止的副作用而执行。 Now the confusing part is that I can not find any guarantees that cb3 and cb4 are guaranteed to execute.现在令人困惑的部分是我找不到任何保证cb3cb4可以执行的保证。 There is nothing I could find that says that requesting stop would atomically change set the stop flag and execute all the callbacks for example.例如,我找不到任何东西说请求停止会自动更改设置停止标志并执行所有回调。 On the other hand, I looked at the Implementation:223 of request_stop and it seems that it does the following另一方面,我查看了request_stopImplementation:223 ,似乎它执行以下操作

  • takes a lock拿锁
  • set the stop flag and fetch the last registered stop callback设置停止标志并获取最后注册的停止回调
  • release the lock释放锁
  • run the callback运行回调
  • release the binary semaphore (which the destructor of that callback waits on)释放二进制信号量(该回调的析构函数等待)
  • tries to reacquire the lock to execute the next callback尝试重新获取锁以执行下一个回调

Now finally to the question, according to the above, the execution of cb3 and cb4 is racing with their destructors (at least cb3 , because cb4 will be chosen for execution under the same lock that will be used to set the stop flag, but again I could not find that guarantee for cb4 mentioned somewhere).现在终于到了这个问题,根据上述, cb3cb4的执行正在与它们的析构函数竞争(至少cb3 ,因为cb4将被选择在用于设置停止标志的同一锁下执行,但又一次我找不到某处提到的cb4的保证)。 So how can one use a stop_callback properly with a jthread without calling request_stop explicitly?那么如何在不显式调用request_stop的情况下正确使用stop_callbackjthread呢? you can't register the callbacks after because they will be destroyed before like cb5 and cb6 and you can't register them withing the thread because they are not guaranteed to execute like cb3 and cb4 and I do not think that it was intended to give them a longer lifetime in the convoluted way of cb1 and cb2您不能在之后注册回调,因为它们会像cb5cb6一样在之前被销毁,并且您不能在线程中注册它们,因为它们不能保证像cb3cb4那样执行,而且我不认为它打算给它们以cb1cb2的复杂方式延长使用寿命

#include <chrono>
#include <iostream>
#include <stop_token>
#include <thread>

int main(int argc, const char * const * const argv)
{
    using namespace std::chrono_literals;

    const auto _cb1 = []() -> void {
        std::cout << "cb1\n";
        std::this_thread::yield();
        std::this_thread::sleep_for(10s);
    };

    const auto _cb2 = []() -> void {
        std::cout << "cb2\n";
        std::this_thread::yield();
        std::this_thread::sleep_for(10s);
    };

    using CB1 =
        decltype(std::stop_callback(std::declval<std::stop_token>(), _cb1));
    using CB2 =
        decltype(std::stop_callback(std::declval<std::stop_token>(), _cb2));

    std::byte storage1[sizeof(CB1)];
    std::byte storage2[sizeof(CB2)];

    const CB1 * cb1 = nullptr;
    const CB2 * cb2 = nullptr;

    {

        std::jthread worker([](const std::stop_token & stop_token) {
            std::stop_callback cb3(stop_token, [] {
                std::cout << "cb3\n";
                std::this_thread::yield();
                std::this_thread::sleep_for(10s);
            });
            std::stop_callback cb4(stop_token, [] {
                std::cout << "cb4\n";
                std::this_thread::yield();
                std::this_thread::sleep_for(10s);
            });

            while (!stop_token.stop_requested())
            {
            }
        });


        cb1 = new (&storage1) std::stop_callback(worker.get_stop_token(), _cb1);

        cb2 = new (&storage2) std::stop_callback(worker.get_stop_token(), _cb2);


        std::stop_callback cb5(worker.get_stop_token(), [] {
            std::cout << "cb5\n";
            std::this_thread::yield();
            std::this_thread::sleep_for(10s);
        });

        std::stop_callback cb6(worker.get_stop_token(), [] {
            std::cout << "cb6\n";
            std::this_thread::yield();
            std::this_thread::sleep_for(10s);
        });

        std::this_thread::sleep_for(2s);
    }

    cb1->~CB1();
    cb2->~CB2();

    return 0;
}

The standard directly states that :该标准直接指出

A call to request_stop that returns true synchronizes with a call to stop_requested on an associated stop_token or stop_source object that returns true.返回 true 的request_stop调用与返回 true 的关联stop_tokenstop_source stop_requested上的 stop_requested 调用同步。

This means that a call to stop_requested that returns true "happens after" any request_stop that returns true .这意味着对返回truestop_requested的调用“发生在”任何返回truerequest_stop之后。 So your while loop cannot exit until a call to request_stop actually returns true to some thread.因此,您的while循环无法退出,直到对request_stop的调用实际上将true返回给某个线程。 More importantly, it cannot exit until such a call returns .更重要的是,在这样的调用返回之前它不能退出。

request_stop is explicitly stated to: request_stop 明确声明为:

If the request was made, the callbacks registered by associated stop_callback objects are synchronously called.如果发出请求,则同步调用关联的stop_callback对象注册的回调。

Being called "synchronously" means exactly that: this function will either call them on the thread requesting the stop or it will synchronize with whatever thread(s) they do get called on.被“同步”调用意味着:这个 function 要么在请求停止的线程上调用它们,要么与它们被调用的任何线程同步。 The main point being that until those callbacks are finished, this function does not return.主要的一点是,在这些回调完成之前,这个 function 不会返回。

And until this function returns, stop_requested will not return true , as previously stated.并且在此 function 返回之前, stop_requested不会返回true ,如前所述。

So there is no data race.所以不存在数据竞赛。 The callbacks will not be destroyed before the thread exits.在线程退出之前,回调不会被销毁。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM