简体   繁体   English

如何在循环中使用std :: condition_variable

[英]How to use std::condition_variable in a loop

I'm trying to implement some algorithm using threads that must be synchronized at some moment. 我正在尝试使用必须在某个时刻同步的线程来实现某些算法。 More or less the sequence for each thread should be: 或多或少每个线程的序列应该是:

1. Try to find a solution with current settings.
2. Synchronize solution with other threads.
3. If any of the threads found solution end work.
4. (empty - to be inline with example below)
5. Modify parameters for algorithm and jump to 1.

Here is a toy example with algorithm changed to just random number generation - all threads should end if at least one of them will find 0. 这是一个玩具示例,其算法更改为随机数生成 - 如果其中至少有一个找到0,则所有线程都应该结束。

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

const int numOfThreads = 8;

std::condition_variable cv1, cv2;
std::mutex m1, m2;
int lockCnt1 = 0;
int lockCnt2 = 0;

int solutionCnt = 0;

void workerThread()
{
    while(true) {
        // 1. do some important work
        int r = rand() % 1000;

        // 2. synchronize and get results from all threads
        {
            std::unique_lock<std::mutex> l1(m1);
            ++lockCnt1;
            if (r == 0) ++solutionCnt; // gather solutions
            if (lockCnt1 == numOfThreads) {
                // last thread ends here
                lockCnt2 = 0;
                cv1.notify_all();
            }
            else {
                cv1.wait(l1, [&] { return lockCnt1 == numOfThreads; });
            }
        }

        // 3. if solution found then quit all threads
        if (solutionCnt > 0) return;

        // 4. if not, then set lockCnt1 to 0 to have section 2. working again
        {
            std::unique_lock<std::mutex> l2(m2);
            ++lockCnt2;
            if (lockCnt2 == numOfThreads) {
                // last thread ends here
                lockCnt1 = 0;
                cv2.notify_all();
            }
            else {
                cv2.wait(l2, [&] { return lockCnt2 == numOfThreads; });
            }
        }

        // 5. Setup new algorithm parameters and repeat.
    }
}

int main()
{
    srand(time(NULL));

    std::vector<std::thread> v;
    for (int i = 0; i < numOfThreads ; ++i) v.emplace_back(std::thread(workerThread));
    for (int i = 0; i < numOfThreads ; ++i) v[i].join();

    return 0;
}

The questions I have are about sections 2. and 4. from code above. 我的问题是关于上面代码的第2节和第4节。

A) In a section 2 there is synchronization of all threads and gathering solutions (if found). A)在第2节中,所有线程和收集解决方案(如果找到)都是同步的。 All is done using lockCnt1 variable. 全部使用lockCnt1变量完成。 Comparing to single use of condition_variable I found it hard how to set lockCnt1 to zero safely, to be able to reuse this section (2.) next time. 与单次使用condition_variable相比,我发现如何安全地将lockCnt1设置为零,以便下次能够重用此部分(2.)。 Because of that I introduced section 4. Is there better way to do that (without introducing section 4.)? 因此,我介绍了第4节。是否有更好的方法(不引入第4节)?

B) It seems that all examples shows using condition_variable rather in context of 'producer-consumer' scenario. B)似乎所有示例都显示使用condition_variable而不是“生产者 - 消费者”情景的上下文。 Is there better way to synchronization all threads in case where all are 'producers'? 在所有线程都是“生产者”的情况下,是否有更好的方法来同步所有线程?

Edit: Just to be clear, I didn't want to describe algorithm details since this is not important here - anyway this is necessary to have all solution(s) or none from given loop execution and mixing them is not allowed. 编辑:为了清楚起见,我不想描述算法细节,因为这在这里并不重要 - 无论如何,这对于给定循环执行所有解决方案都是必要的,并且不允许混合它们。 Described sequence of execution must be followed and the question is how to have such synchronization between threads. 必须遵循描述的执行顺序,问题是如何在线程之间进行这种同步。

A) You could just not reset the lockCnt1 to 0, just keep incrementing it further. A)你可以不将lockCnt1重置为0,只是继续增加它。 The condition lockCnt2 == numOfThreads then changes to lockCnt2 % numOfThreads == 0 . 条件lockCnt2 == numOfThreads然后更改为lockCnt2 % numOfThreads == 0 You can then drop the block #4. 然后你可以删除块#4。 In future you could also use std::experimental::barrier to get the threads to meet. 将来你也可以使用std::experimental::barrier来获得满足的线程。

B) I would suggest using std::atomic for solutionCnt and then you can drop all other counters, the mutex and the condition variable. B)我建议使用std::atomic for solutionCnt然后你可以删除所有其他计数器,互斥锁和条件变量。 Just atomically increase it by one in the thread that found solution and then return. 只需在找到解决方案的线程中以原子方式将其增加一,然后返回。 In all threads after every iteration check if the value is bigger than zero. 在每次迭代后的所有线程中检查该值是否大于零。 If it is, then return. 如果是,则返回。 The advantage is that the threads do not have to meet regularly, but can try to solve it at their own pace. 优点是线程不必定期会面,但可以尝试按照自己的节奏解决它。

Out of curiosity, I tried to solve your problem using std::async . 出于好奇,我尝试使用std::async解决您的问题。 For every attempt to find a solution, we call async . 对于每次尝试找到解决方案,我们称之为async Once all parallel attempts have finished, we process feedback, adjust parameters, and repeat. 完成所有并行尝试后,我们处理反馈,调整参数并重复。 An important difference with your implementation is that feedback is processed in the calling (main) thread. 与您的实现的一个重要区别是反馈在调用(主)线程中处理。 If processing feedback takes too long — or if we don't want to block the main thread at all — then the code in main() can be adjusted to also call std::async . 如果处理反馈花费的时间太长 - 或者如果我们根本不想阻塞主线程 - 那么main()的代码可以调整为也调用std::async

The code is supposed to be quite efficient, provided that the implementation of async uses a thread pool (eg Microsoft's implementation does that). 如果async的实现使用线程池(例如Microsoft的实现那样做),那么代码应该是非常有效的。

#include <chrono>
#include <future>
#include <iostream>
#include <vector>

const int numOfThreads = 8;

struct Parameters{};
struct Feedback {
    int result;
};

Feedback doTheWork(const Parameters &){
    // do the work and provide result and feedback for future runs
    return Feedback{rand() % 1000};
}

bool isSolution(const Feedback &f){
    return f.result == 0;
}

// Runs doTheWork in parallel. Number of parallel tasks is same as size of params vector
std::vector<Feedback> findSolutions(const std::vector<Parameters> &params){

    // 1. Run async tasks to find solutions. Normally threads are not created each time but re-used from a pool
    std::vector<std::future<Feedback>> futures;
    for (auto &p: params){
        futures.push_back(std::async(std::launch::async,
                                    [&p](){ return doTheWork(p); }));
    }

    // 2. Syncrhonize: wait for all tasks
    std::vector<Feedback> feedback(futures.size());
    for (auto nofRunning = futures.size(), iFuture = size_t{0}; nofRunning > 0; ){

        // Check if the task has finished (future is invalid if we already handled it during an earlier iteration)
        auto &future = futures[iFuture];
        if (future.valid() && future.wait_for(std::chrono::milliseconds(1)) != std::future_status::timeout){
            // Collect feedback for next attempt
            // Alternatively, we could already check if solution has been found and cancel other tasks [if our algorithm supports cancellation]
            feedback[iFuture] = std::move(future.get());
            --nofRunning;
        }

        if (++iFuture == futures.size())
            iFuture = 0;
    }
    return feedback;
}

int main()
{
    srand(time(NULL));

    std::vector<Parameters> params(numOfThreads);
    // 0. Set inital parameter values here

    // If we don't want to block the main thread while the algorithm is running, we can use std::async here too
    while (true){
        auto feedbackVector = findSolutions(params);
        auto itSolution = std::find_if(std::begin(feedbackVector), std::end(feedbackVector), isSolution);

        // 3. If any of the threads has found a solution, we stop
        if (itSolution != feedbackVector.end())
            break;

        // 5. Use feedback to re-configure parameters for next iteration
    }

    return 0;
}

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

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