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

时间:2019-06-28 12:38:50

标签: c++ multithreading condition-variable

我正在尝试使用必须在某一时刻同步的线程来实现某些算法。每个线程的顺序大致应为:

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.

这是一个玩具示例,算法更改为仅生成随机数-如果其中至少一个找到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;
}

我的问题是关于上面代码中的第2和第4节。

A)在第2节中,所有线程都同步并且收集解决方案(如果找到)。所有操作都使用lockCnt1变量完成。与单次使用condition_variable相比,我发现很难安全地将lockCnt1设置为零,以便下次可以重用此部分(2.)。因此,我介绍了第4节。是否有更好的方法(不介绍第4节)呢?

B)似乎所有示例都显示了使用condition_variable而不是在“生产者-消费者”方案的上下文中。如果所有都是“生产者”,是否有更好的方法同步所有线程?

编辑:为了清楚起见,我不想描述算法细节,因为这在这里并不重要-无论如何,对于所有解决方案都是必需的,否则就不能从给定的循环执行中混合任何解决方案。必须遵循描述的执行顺序,问题是如何在线程之间实现这种同步。

2 个答案:

答案 0 :(得分:1)

A)您不能仅将lockCnt1重置为0,而只能继续增加它。然后条件lockCnt2 == numOfThreads变为lockCnt2 % numOfThreads == 0。然后,您可以放下#4块。将来,您还可以使用std::experimental::barrier来使线程满足要求。

B)我建议对std::atomic使用solutionCnt,然后可以删除所有其他计数器,互斥量和条件变量。只需在找到解决方案的线程中以原子方式将其增加一,然后返回即可。每次迭代后,在所有线程中检查该值是否大于零。如果是,则返回。优点是线程不必定期开会,而是可以尝试按自己的步调解决它。

答案 1 :(得分:0)

出于好奇,我尝试使用std::async解决您的问题。每次寻找解决方案的尝试,我们都叫async。完成所有并行尝试后,我们将处理反馈,调整参数并重复。与实现的一个重要区别是,反馈是在调用(主)线程中处理的。如果处理反馈的时间太长(或者我们根本不想阻塞主线程),那么可以将main()中的代码调整为也调用std::async

假设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;
}