C++ 条件变量与向量化的新线程
C++ condition variables vs new threads for vectorization
我有一个循环的代码块。代码的一部分对数据向量进行操作,我想对该操作进行向量化。这个想法是将数组的细化拆分到多个线程上,这些线程将处理数组的子部分。我必须在两种可能性之间做出决定。第一个是每次遇到此部分时创建线程,并在最后与主线程重新加入它们:
for(....)
{
//serial stuff
//crate threads
for(i = 0; i < num_threads; ++i)
{
threads_vect.push_back(std::thread(f, sub_array[i]));
}
//join them
for(auto& t : threads_vect)
{
t.join();
}
//serial stuff
}
这与使用 OpenMP 所做的类似,但由于问题很简单,我想使用 std::threads 而不是 OpenMP(除非有充分的理由反对这样做)。
第二种方案是预先创建线程以避免创建和销毁的开销,并使用条件变量进行同步(同步我省略了很多东西,这只是一般的想法):
std::condition_variable cv_threads;
std::condition_variable cv_main;
//create threads, the will be to sleep on cv_threads
for(....)
{
//serial stuff
//wake up threads
cv_threads.notify_all();
//sleep until the last thread finishes, that will notify.
main_thread_lock.lock();
cv_main.wait(main_lock);
//serial stuff
}
为了允许并行性,线程必须在计算开始时一醒来就解锁 thread_lock,然后再次获取它以进入睡眠状态并在它们之间同步以通知主线程。
我的问题是,在这样的上下文中,通常首选哪种解决方案,如果避免线程创建和销毁的开销通常值得增加复杂性(或者完全值得,因为增加的同步也会增加时间).
显然这也取决于每个线程的计算时间,但这可能会有很大差异,因为数据向量的长度也可能非常短(每个线程大约两个元素,这将导致计算时间约15毫秒)。
创建新线程的最大缺点是线程创建和关闭通常非常昂贵。想一想操作系统为使线程启动而必须做的所有事情,与通知条件变量所需的事情相比。
请注意,总是需要同步,创建线程也是如此。 C++11 std::thread
for instances 在构造时引入了与创建线程的 synchronizes-with 关系。因此,您可以放心地假设,无论您的实现如何,线程创建总是比条件变量信号发送昂贵得多。
像 OpenMP 这样的框架通常会尝试以某种方式分摊这些成本。例如,OpenMP 实现不需要在每次循环后关闭工作线程,许多实现不会这样做。
我有一个循环的代码块。代码的一部分对数据向量进行操作,我想对该操作进行向量化。这个想法是将数组的细化拆分到多个线程上,这些线程将处理数组的子部分。我必须在两种可能性之间做出决定。第一个是每次遇到此部分时创建线程,并在最后与主线程重新加入它们:
for(....)
{
//serial stuff
//crate threads
for(i = 0; i < num_threads; ++i)
{
threads_vect.push_back(std::thread(f, sub_array[i]));
}
//join them
for(auto& t : threads_vect)
{
t.join();
}
//serial stuff
}
这与使用 OpenMP 所做的类似,但由于问题很简单,我想使用 std::threads 而不是 OpenMP(除非有充分的理由反对这样做)。
第二种方案是预先创建线程以避免创建和销毁的开销,并使用条件变量进行同步(同步我省略了很多东西,这只是一般的想法):
std::condition_variable cv_threads;
std::condition_variable cv_main;
//create threads, the will be to sleep on cv_threads
for(....)
{
//serial stuff
//wake up threads
cv_threads.notify_all();
//sleep until the last thread finishes, that will notify.
main_thread_lock.lock();
cv_main.wait(main_lock);
//serial stuff
}
为了允许并行性,线程必须在计算开始时一醒来就解锁 thread_lock,然后再次获取它以进入睡眠状态并在它们之间同步以通知主线程。
我的问题是,在这样的上下文中,通常首选哪种解决方案,如果避免线程创建和销毁的开销通常值得增加复杂性(或者完全值得,因为增加的同步也会增加时间).
显然这也取决于每个线程的计算时间,但这可能会有很大差异,因为数据向量的长度也可能非常短(每个线程大约两个元素,这将导致计算时间约15毫秒)。
创建新线程的最大缺点是线程创建和关闭通常非常昂贵。想一想操作系统为使线程启动而必须做的所有事情,与通知条件变量所需的事情相比。
请注意,总是需要同步,创建线程也是如此。 C++11 std::thread
for instances 在构造时引入了与创建线程的 synchronizes-with 关系。因此,您可以放心地假设,无论您的实现如何,线程创建总是比条件变量信号发送昂贵得多。
像 OpenMP 这样的框架通常会尝试以某种方式分摊这些成本。例如,OpenMP 实现不需要在每次循环后关闭工作线程,许多实现不会这样做。