我正在尝试使用必须在某一时刻同步的线程来实现某些算法。每个线程的顺序大致应为:
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而不是在“生产者-消费者”方案的上下文中。如果所有都是“生产者”,是否有更好的方法同步所有线程?
编辑:为了清楚起见,我不想描述算法细节,因为这在这里并不重要-无论如何,对于所有解决方案都是必需的,否则就不能从给定的循环执行中混合任何解决方案。必须遵循描述的执行顺序,问题是如何在线程之间实现这种同步。
答案 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> ¶ms){
// 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;
}