简体   繁体   English

多个线程等待所有线程完成,直到新工作开始

[英]Multiple threads waiting for all to finish till new work is started

I am trying to create a sort of threadpool that runs functions on separate threads and only starts a new iteration when all functions have finished.我正在尝试创建一种线程池,它在单独的线程上运行函数,并且只有在所有函数完成后才开始新的迭代。

map<size_t, bool> status_map;
vector<thread> threads;
condition_variable cond;

bool are_all_ready() {
  mutex m;
  unique_lock<mutex> lock(m);
  for (const auto& [_, status] : status_map) {
    if (!status) {
      return false;
    }
  }
  return true;
}

void do_little_work(size_t id) {
  this_thread::sleep_for(chrono::seconds(1));
  cout << id << " did little work..." << endl;
}

void do_some_work(size_t id) {
  this_thread::sleep_for(chrono::seconds(2));
  cout << id << " did some work..." << endl;
}

void do_much_work(size_t id) {
  this_thread::sleep_for(chrono::seconds(4));
  cout << id << " did much work..." << endl;
}

void run(const function<void(size_t)>& function, size_t id) {
  while (true) {
    mutex m;
    unique_lock<mutex> lock(m);

    cond.wait(lock, are_all_ready);

    status_map[id] = false;
    cond.notify_all();

    function(id);

    status_map[id] = true;
    cond.notify_all();
  }
}
 
int main() {
  threads.push_back(thread(run, do_little_work, 0));
  threads.push_back(thread(run, do_some_work, 1));
  threads.push_back(thread(run, do_much_work, 2));

  for (auto& thread : threads) {
    thread.join();
  }

  return EXIT_SUCCESS;
}

I expect to get the output:我希望得到 output:

0 did little work...
1 did some work...
2 did much work...
0 did little work...
1 did some work...
2 did much work...
        .
        .
        .

after the respective timeouts but when I run the program I only get在各自的超时之后但是当我运行程序时我只得到

0 did little work...
0 did little work...
        .
        .
        .

I also have to say that Im rather new to multithreading but in my understanding, the condition_variable should to the taks of blocking every thread till the predicate returns true.我还不得不说,我对多线程相当陌生,但据我了解, condition_variable应该用于阻塞每个线程,直到谓词返回 true。 And in my case are_all_ready should return true after all functions have returned.在我的情况下, are_all_ready应该在所有函数都返回后返回 true。

As-is, your program has a crash (UB) due to concurrent access to status_map .照原样,由于对status_map的并发访问,您的程序发生了崩溃 (UB)。

When you do:当你这样做时:

void run(const function<void(size_t)>& function, size_t id)
{
...
    mutex m;
    unique_lock<mutex> lock(m);
...
    status_map[id] = false;

the lock s created are local variables, one per thread, and as such independent.创建的lock是局部变量,每个线程一个,因此是独立的。 So, it doesn't prevent multiple threads from writing to status_map at once, and thus crashing.因此,它不会阻止多个线程一次写入status_map ,从而导致崩溃。 That's what I get on my machine.这就是我在我的机器上得到的。

Now, if you make the mutex static, only one thread can access the map at once.现在,如果你创建mutex static,一次只有一个线程可以访问 map。 But that also makes it so that only one thread runs at once.但这也使得一次只有一个线程运行。 With this I see 0, 1 and 2 running, but only once at a time and a strong tendency for the previous thread to have run to run again.有了这个,我看到 0、1 和 2 正在运行,但一次只运行一次,并且前一个线程很有可能再次运行。

My suggestion, go back to the drawing board and make it simpler.我的建议,go 回到绘图板上,让它更简单。 All threads run at once, single mutex to protect the map, only lock the mutex to access the map, and... well, in fact, I don't even see the need for a condition variable.所有线程同时运行,单个互斥锁保护 map,仅锁定互斥锁以访问 map,而且......好吧,事实上,我什至不认为需要条件变量。

eg what is wrong with:例如有什么问题:

#include <thread>
#include <iostream>
#include <vector>

using namespace std;

vector<thread> threads;

void do_little_work(size_t id) {
  this_thread::sleep_for(chrono::seconds(1));
  cout << id << " did little work..." << endl;
}

void do_some_work(size_t id) {
  this_thread::sleep_for(chrono::seconds(2));
  cout << id << " did some work..." << endl;
}

void do_much_work(size_t id) {
  this_thread::sleep_for(chrono::seconds(4));
  cout << id << " did much work..." << endl;
}

void run(const function<void(size_t)>& function, size_t id) {
  while (true) {
    function(id);
  }
}

int main() {
  threads.push_back(thread(run, do_little_work, 0));
  threads.push_back(thread(run, do_some_work, 1));
  threads.push_back(thread(run, do_much_work, 2));

  for (auto& thread : threads) {
    thread.join();
  }

  return EXIT_SUCCESS;
}

There are several ways to do this.有几种方法可以做到这一点。

Easiest in my opinion would be a C++20 std::barrier , which says, "wait until all of N threads have arrived and are waiting here."在我看来,最简单的是 C++20 std::barrier ,它表示“等到所有N个线程都到达并在这里等待”。

#include <barrier>

std::barrier synch_workers(3);
....
void run(const std::function<void(size_t)>& func, size_t id) {
  while (true) {
    synch_workers.arrive_and_wait(); // wait for all three to be ready
    func(id);
  }
}

Cruder and less efficient, but equally effective, would be to construct and join() new sets of three worker threads for each "batch" of work:更粗鲁且效率较低但同样有效的方法是为每个“批次”工作构建和join()三个工作线程的新集合:

int main(...) {
  std::vector<thread> threads;
  ...
  while (flag_running) {
    threads.push_back(...);
    threads.push_back(...);
    ...
    for (auto& thread : threads) {
      thread.join();
    }
    threads.clear();
  }

Aside在旁边

I'd suggest you revisit some core synchronization concepts, however.不过,我建议您重新审视一些核心同步概念。 You are using new mutexes when you want to re-use a shared one.当您想重新使用共享的互斥锁时,您正在使用新的互斥锁。 The scope of your unique_lock isn't quite right.您的 unique_lock 的unique_lock不太正确。

Now, your idea to track worker thread "busy/idle" state in a map is straightforward, but cannot correctly coordinate "batches" or "rounds" of work that must be begun at the same time.现在,您在 map 中跟踪工作线程“忙碌/空闲” map的想法很简单,但无法正确协调必须同时开始的“批次”或“轮次”工作。

If a worker sees in the map that two of three threads, including itself, are "idle", what does that mean?如果工作人员在map中看到三个线程中的两个(包括其自身)处于“空闲”状态,这意味着什么? Is a "batch" of work concluding — ie, two workers are waiting for a tardy third? “一批”工作是否已经结束——即,两名工人正在等待第三个迟到的工人? Or has a batch just begun — ie, the two idle threads are tardy and had better get to work like their more eager peer?或者一个批处理刚刚开始——即,两个空闲线程迟到了,最好像他们更急切的对等一样开始工作?

The threads cannot know the answer without keeping track of the current batch of work, which is what a barrier (or its more complex cousin the phaser ) does under the hood.如果不跟踪当前的工作批次,线程就无法知道答案,这就是障碍(或其更复杂的表亲移相器)在幕后所做的。

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

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