【问题标题】:std::thread: How to wait (join) for any of the given threads to complete?std::thread:如何等待(加入)任何给定线程完成?
【发布时间】:2017-04-25 15:13:47
【问题描述】:

例如,我有两个线程,t1t2。我想等待t1 t2 完成。这可能吗?

如果我有一系列线程,比如std::vector<std::thread>,我该怎么做?

【问题讨论】:

  • 您有 minimal reproducible example 证明您尝试自己解决这个问题吗?
  • 我不知道有任何标准机制可以直接执行此操作。你真正想解决什么问题?或许还有其他解决方案。
  • @KerrekSB 将阻塞所有线程。我认为,如果您想阻止 一个 线程(n 个)坚持使用std::thread,您将需要一个外部变量(如std::atomic_bool)作为信号。
  • 啊,对,你只想阻塞直到第一个线程完成......没关系!
  • @t123yh 也许是 POSIX ?

标签: c++ multithreading c++11


【解决方案1】:

总是有等待和通知使用std::condition_variable例如

std::mutex m;
std::condition_variable cond;
std::atomic<std::thread::id> val;

auto task = [&] {
    std::this_thread::sleep_for(1s); // Some work

    val = std::this_thread::get_id();
    cond.notify_all();
};

std::thread{task}.detach();
std::thread{task}.detach();
std::thread{task}.detach();

std::unique_lock<std::mutex> lock{m};
cond.wait(lock, [&] { return val != std::thread::id{}; });

std::cout << "Thread " << val << " finished first" << std::endl;

注意: val 不一定代表首先完成的线程,因为所有线程大约在同一时间完成并且可能发生覆盖,但这仅用于目的这个例子。

【讨论】:

  • 但是请注意,分离线程是一个设计的决定,而不是编码方便,而且很少合适。在这种情况下,它是不需要的。一旦主线程唤醒,它就可以加入三个线程。
  • 赞成。一旦您将.join() 视为不是魔术,而只是用于在具有互斥锁和条件变量的线程之间进行通信的语法糖,这就是要走的路。如果.join() 的默认行为不是您想要的,请自行选择。
  • 完整示例:主监控10个线程1s。平均而言,他们中的一半会在 1 秒内完成工作,而另一半不会。 main 为每个完成的线程打印结果,并在 1 秒后协同取消其余线程。然后 main 在返回之前等待所有取消的线程返回。不需要原子。只是一个有限状态机与互斥锁和条件变量进行线程间(双向)通信。很容易根据需要扩展此示例的通信丰富性。 wandbox.org/permlink/5RjOy09nY89zqTwv
  • Howard Hinnant:据我了解,join() 神奇(从库用户的角度来看),因为线程本地对象的析构函数保证已经完成。这通常不能在用户代码中模拟。
【解决方案2】:

不,在 C++11 的线程库中无需等待多个等效对象。

如果您想等待一组操作中的第一个,请考虑让它们提供线程安全的生产者-消费者队列。

Here 是我发布的包含threaded_queue&lt;T&gt; 的帖子。将您的线程的工作产品交付给这样的队列。让消费者读出另一端。

现在有人可以同时等待多个线程(工作产品)。或者一根线。或 GPU 着色器。或者通过 RESTful Web 界面交付工作产品。你不在乎。

线程本身应该由线程池或std::thread 之上的其他更高级别抽象之类的东西管理,因为std::thread 是一个糟糕的面向客户端的线程抽象。

template<class T>
struct threaded_queue {
  using lock = std::unique_lock<std::mutex>;
  void push_back( T t ) {
    {
      lock l(m);
      data.push_back(std::move(t));
    }
    cv.notify_one();
  }
  boost::optional<T> pop_front() {
    lock l(m);
    cv.wait(l, [this]{ return abort || !data.empty(); } );
    if (abort) return {};
    auto r = std::move(data.back());
    data.pop_back();
    return r;
  }
  void terminate() {
    {
      lock l(m);
      abort = true;
      data.clear();
    }
    cv.notify_all();
  }
  ~threaded_queue()
  {
    terminate();
  }
private:
  std::mutex m;
  std::deque<T> data;
  std::condition_variable cv;
  bool abort = false;
};

我会在 C++17 中使用 std::optional 而不是 boost::optional。它也可以替换为 unique_ptr 或许多其他构造。

【讨论】:

    【解决方案3】:

    轮询等待很容易做到:

    #include<iostream>
    #include<thread>
    #include<random>
    #include<chrono>
    #include<atomic>
    
    void thread_task(std::atomic<bool> & boolean) {
        std::default_random_engine engine{std::random_device{}()};
        std::uniform_int_distribution<int64_t> dist{1000, 3000};
        int64_t wait_time = dist(engine);
        std::this_thread::sleep_for(std::chrono::milliseconds{wait_time});
        std::string line = "Thread slept for " + std::to_string(wait_time) + "ms.\n";
        std::cout << line;
        boolean.store(true);
    }
    
    int main() {
        std::vector<std::thread> threads;
        std::atomic<bool> boolean{false};
        for(int i = 0; i < 4; i++) {
            threads.emplace_back([&]{thread_task(boolean);});
        }
        std::string line = "We reacted after a single thread finished!\n";
        while(!boolean) std::this_thread::yield();
        std::cout << line;
        for(std::thread & thread : threads) {
            thread.join();
        }
        return 0;
    }
    

    我在 Ideone.com 上获得的示例输出:

    Thread slept for 1194ms.
    We reacted after a single thread finished!
    Thread slept for 1967ms.
    Thread slept for 2390ms.
    Thread slept for 2984ms.
    

    这可能不是最好的代码,因为轮询循环不一定是最佳实践,但它应该作为一个开始。

    【讨论】:

    • 这可以通过使用std::condition_variable 而不是使用while 循环轮询来改进
    • @rwols 如果您知道该代码的外观,您可以将其作为答案提交。我还不太习惯使用条件变量。
    【解决方案4】:

    没有标准的多线程等待方式。

    您需要借助操作系统特定的功能,例如 Windows 上的WaitForMultipleObjects。 仅适用于 Windows 的示例:

    HANDLE handles[] = { t1.native_handle(), t2.native_handle(),  };
    auto res = WaitForMultipleObjects(2 , handles, FALSE, INFINITE);
    

    有趣的是,当std::when_any 将被标准化时,可以做一个标准但浪费的解决方案:

    std::vector<std::thread> waitingThreads;
    std::vector<std::future<void>> futures;
    for (auto& thread: threads){
        std::promise<void> promise;
        futures.emplace_back(promise.get_future());
        waitingThreads.emplace_back([&thread, promise = std::move(promise)]{
             thread.join();
             promise.set_value();
        });
    }
    
    auto oneFinished = std::when_any(futures.begin(), futures.end());
    

    非常浪费,仍然不可用,但标准。

    【讨论】:

    • 您的意思是 thread 而不是 waitingThreads.emplace lambda 中的 t? (或t 而不是thread 在循环中?
    猜你喜欢
    • 1970-01-01
    • 2017-12-16
    • 2010-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-18
    • 1970-01-01
    相关资源
    最近更新 更多