繁体   English   中英

std :: atomic_bool用于取消标志:是std :: memory_order_relaxed正确的内存顺序?

[英]std::atomic_bool for cancellation flag: is std::memory_order_relaxed the correct memory order?

我有一个从套接字读取并生成数据的线程。 在每次操作之后,线程检查std::atomic_bool标志以查看它是否必须提前退出。

为了取消操作,我将取消标志设置为true ,然后在工作线程对象上调用join()

线程代码和取消函数看起来像这样:

std::thread work_thread;
std::atomic_bool cancel_requested{false};

void thread_func()
{
   while(! cancel_requested.load(std::memory_order_relaxed))
      process_next_element();

}

void cancel()
{
    cancel_requested.store(true, std::memory_order_relaxed);
    work_thread.join();
}

std::memory_order_relaxed是否使用了原子变量的正确内存顺序?

只要在cancel_requested标志和其他任何内容之间没有依赖关系,您就应该是安全的。

如图所示的代码看起来没问题, 假设您仅使用cancel_requested来加速关闭,而且还有一个有序关闭的设置,例如队列中的sentinel条目(当然队列本身是同步的)。

这意味着您的代码实际上如下所示:

std::thread work_thread;
std::atomic_bool cancel_requested{false};
std::mutex work_queue_mutex;
std::condition_variable work_queue_filled_cond;
std::queue work_queue;

void thread_func()
{
    while(! cancel_requested.load(std::memory_order_relaxed))
    {
        std::unique_lock<std::mutex> lock(work_queue_mutex);
        work_queue_filled_cond.wait(lock, []{ return !work_queue.empty(); });
        auto element = work_queue.front();
        work_queue.pop();
        lock.unlock();
        if (element == exit_sentinel)
            break;
        process_next_element(element);
    }
}

void cancel()
{
    std::unique_lock<std::mutex> lock(work_queue_mutex);
    work_queue.push_back(exit_sentinel);
    work_queue_filled_cond.notify_one();
    lock.unlock();
    cancel_requested.store(true, std::memory_order_relaxed);
    work_thread.join();
}

如果我们那么远,那么cancel_requested也可能成为常规变量,代码甚至变得更简单。

std::thread work_thread;
bool cancel_requested = false;
std::mutex work_queue_mutex;
std::condition_variable work_queue_filled_cond;
std::queue work_queue;

void thread_func()
{
    while(true)
    {
        std::unique_lock<std::mutex> lock(work_queue_mutex);
        work_queue_filled_cond.wait(lock, []{ return cancel_requested || !work_queue.empty(); });
        if (cancel_requested)
            break;
        auto element = work_queue.front();
        work_queue.pop();
        lock.unlock();
        process_next_element(element);
    }
}

void cancel()
{
    std::unique_lock<std::mutex> lock(work_queue_mutex);
    cancel_requested = true;
    work_queue_filled_cond.notify_one();
    lock.unlock();
    work_thread.join();
}

memory_order_relaxed通常很难推理,因为它模糊了顺序执行代码的一般概念。 因此,赫伯在他的原子武器谈话中解释说,它的用处非常非常有限。

注意std::thread::join()本身充当两个线程之间的内存屏障。

这段代码是否正确取决于很多事情。 最重要的是它取决于“正确”的确切含义。 据我所知,您显示的代码位不会调用未定义的行为(假设您的work_threadcancel_requested实际上没有按照上面的代码段建议的顺序进行初始化,因为您可能会让线程读取未初始化的值。原子)。 如果您需要做的就是更改该标志的值并让线程最终在某个时刻看到新值而不管其他任何可能发生的值,那么std::memory_order_relaxed就足够了。

但是,我看到你的工作线程调用了process_next_element()函数。 这表明工作线程有一些机制可以接收要处理的元素。 在处理完所有元素后,我没有看到线程退出的任何方法。 当没有下一个元素可用时, process_next_element()做什么? 它会立即返回吗? 在这种情况下,你有一个忙等待更多的输入或取消,这将工作但可能不理想。 或者process_next_element()内部调用一些阻塞的函数,直到一个元素可用为止!? 如果是这种情况,那么取消线程必须首先设置取消标志,然后做任何需要确保下一个元素调用你的线程可能阻止返回。 在这种情况下,在阻塞调用返回后,线程永远不会看到取消标志是很重要的。 否则,您可能会返回调用,返回循环,仍然读取旧的取消标志,然后再次调用process_next_element() 如果保证process_next_element()再次返回,那么你没事。 如果不是这样,那你就陷入了僵局。 所以我认为它在技术上取决于process_next_element()确切含义。 人们可以想象的实现process_next_element()你将有可能需要比宽松内存顺序的更多。 但是,如果您已经有一个获取要处理的新元素的机制,为什么甚至使用单独的取消标记? 您可以通过相同的机制简单地处理取消,例如,让它返回具有特殊值的下一个元素,或者根本不返回任何元素来表示取消处理并导致线程返回而不是依赖于单独的标志......

暂无
暂无

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

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