代码之家  ›  专栏  ›  技术社区  ›  Paul Belanger

std::atomic\u bool for cancellation标志:std::memory\u order\u released是否为正确的内存顺序?

  •  5
  • Paul Belanger  · 技术社区  · 6 年前

    我有一个线程从套接字读取数据并生成数据。每次操作后,线程都会检查 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

    2 回复  |  直到 6 年前
        1
  •  5
  •   rustyx    6 年前

    cancel_requested 旗帜和 ,你应该是安全的。

    如图所示的代码看起来正常, 假设 你用 取消请求 不仅可以加快关闭,而且还可以有序关闭,例如队列中的哨兵条目(当然,队列本身是同步的)。

    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();
    }
    

    如果我们有那么远,那么 取消请求

    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 通常很难推理,因为它模糊了顺序执行代码的一般概念。所以它的用处是非常非常有限的,赫伯在他的书中解释道 atomic weapons talk .

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

        2
  •  4
  •   Michael Kenzel    6 年前

    work_thread cancel_requested 实际上并没有按照上面代码段建议的顺序初始化,因为这样线程就有可能读取原子的未初始化值)。如果您所需要做的只是更改该标志的值,并让线程最终在某个点看到新值,而不受其他任何情况的影响,那么 std::memory_order_relaxed 这就足够了。

    process_next_element() 功能。这表明工作线程通过某种机制接收要处理的元素。在处理完所有元素后,我看不到线程退出的任何方法。什么是 进程\下一个\元素() 在内部调用某个函数,该函数在元素可用之前一直阻塞!?如果是这种情况,那么取消线程必须首先设置取消标志,然后采取一切必要措施确保线程的下一个元素调用可能会阻塞返回。在这种情况下,在阻塞调用返回后,线程可能永远看不到取消标志。否则,您可能会返回呼叫,返回循环,仍然读取旧的取消标志,然后继续呼叫 进程\下一个\元素() 再一次。如果 进程\下一个\元素() 保证会再回来,那你就没事了。如果情况并非如此,则会出现死锁。所以我相信这在技术上取决于 进程\下一个\元素() 做。一个 想象一下 进程\下一个\元素() 您可能需要的不仅仅是轻松的内存顺序。然而,如果您已经有了获取新元素以进行处理的机制,为什么还要使用单独的取消标志呢?您可以简单地通过相同的机制处理取消,例如,让它返回具有特殊值的下一个元素,或者根本不返回任何元素来表示取消处理,并使线程返回,而不是依赖于单独的标志…