我想重用一个使用不同参数多次调用相同函数的线程向量。没有写入(原子参数除外),因此不需要互斥锁。为了描述这个想法,我创建了一个并行化代码的基本示例,该代码可以找到向量的最大值。有明显更好的方法来找到向量的最大值,但为了解释并避免深入了解我正在编写的实际代码的详细信息,我将使用这个愚蠢的例子。
代码通过调用函数 pFind 来查找向量的最大数量,该函数检查向量是否包含数字 k ( k 是初始化为上限)。如果是,则执行停止,否则 k 减1并重复该过程。
下面的代码生成一个线程向量,用于并行化向量中 k 的搜索。问题是,对于 k 的每个值,每次新线程连接时都会重新生成线程向量。 生成线程向量并每次加入它们都会带来我想要避免的开销。
我想知道是否有一种方法只生成一次线程的向量(池),并将它们重新用于新的执行。任何其他加速提示将不胜感激。
| file_name | column_number | column_name | col_is_good |
|-----------|---------------|-------------|-------------|
| abc.csv | 1 | | 0 |
| abc.csv | 2 | name | 1 |
| abc.csv | 3 | price | 1 |
| abc.csv | 4 | artist | 1 |
| def.csv | 1 | name | 1 |
| def.csv | 2 | | 0 |
| def.csv | 3 | | 0 |
| def.csv | 4 | price | 1 |
答案 0 :(得分:1)
构造后无法为std::thread
分配不同的执行函数(闭包)。这通常适用于所有线程抽象,尽管通常实现会尝试在内部进行memoize或缓存低级抽象,以使线程分叉并快速加入,因此只需构建新线程是可行的。系统编程圈中存在一个争论,即创建一个新线程是否应该非常轻量级,或者是否应该经常将客户端写入fork线程。 (鉴于这种情况已经持续了很长时间,应该很明显涉及到许多权衡因素。)
还有很多其他抽象尝试做你真正想要的事情。它们具有诸如“线程池”,“任务执行器”(或仅“执行器”)和“期货”之类的名称。所有这些都倾向于通过创建一些线程映射到线程上,这些线程通常与系统中的硬件核心数相关,然后让每个线程循环并查找请求。
正如评论所指出的那样,你自己做的主要方法是让线程有一个接受执行请求,处理它们然后发布结果的顶级循环。为此,您需要使用其他同步方法,如互斥锁和条件变量。如果有很多请求并且请求不是非常大,那么以这种方式执行通常会更快。
尽管标准C ++并发支持是一件好事,但它对于真实世界的高性能工作也相当缺乏。像Intel's TBB这样的东西更像是一种工业强度解决方案。
答案 1 :(得分:0)
您的代码存在竞争条件:bool
不是原子类型,因此不可以安全地让多个线程同时写入。您需要使用std::atomic_bool
或std::atomic_flag
。
要回答您的问题,您需要在循环的每次迭代中重新创建threads
向量,您可以通过将其声明移出循环体来避免。重用线程本身是一个更复杂的主题,很难正确或简洁地描述。
vector<thread> threads;
threads.reserve(numTh);
while (!flag) {
for (size_t i = 0; i < numTh; ++i)
threads.emplace_back(pFind, a, size, flag, i, numTh, val);
for (auto &th : threads)
th.join();
threads.clear();
}
答案 2 :(得分:0)
通过将来自不同在线搜索的一些代码拼凑在一起,以下工作,但是 不如 ,就像在每次迭代时重新生成线程的方法一样循环。
也许有人可以评论这种方法。
以下类描述了线程池
class ThreadPool {
public:
ThreadPool(int threads) : shutdown_(false){
threads_.reserve(threads);
for (int i = 0; i < threads; ++i)
threads_.emplace_back(std::bind(&ThreadPool::threadEntry, this, i));
}
~ThreadPool(){
{
// Unblock any threads and tell them to stop
std::unique_lock<std::mutex>l(lock_);
shutdown_ = true;
condVar_.notify_all();
}
// Wait for all threads to stop
std::cerr << "Joining threads" << std::endl;
for (auto & thread : threads_) thread.join();
}
void doJob(std::function<void(void)>func){
// Place a job on the queu and unblock a thread
std::unique_lock<std::mutex>l(lock_);
jobs_.emplace(std::move(func));
condVar_.notify_one();
}
void threadEntry(int i){
std::function<void(void)>job;
while (1){
{
std::unique_lock<std::mutex>l(lock_);
while (!shutdown_ && jobs_.empty()) condVar_.wait(l);
if (jobs_.empty()){
// No jobs to do and we are shutting down
std::cerr << "Thread " << i << " terminates" << std::endl;
return;
}
std::cerr << "Thread " << i << " does a job" << std::endl;
job = std::move(jobs_.front());
jobs_.pop();
}
// Do the job without holding any locks
job();
}
}
};
以下是代码的其余部分
void pFind(
vector<int>& a,
int n,
std::atomic<bool>& flag,
int k,
int numTh,
int val,
std::atomic<int>& completed) {
int i = k;
while (i < n) {
if (a[i] == val) {
flag = true;
break;
} else
i += numTh;
}
completed++;
}
int main() {
std::atomic<bool> flag;
flag = false;
int numTh = 8;
int val = 1000;
int pos = 0;
std::atomic<int> completed;
completed=0;
ThreadPool p(numThreads);
while (!flag) {
for (int i = 0; i < numThreads; i++) {
p.doJob(std::bind(pFind, std::ref(a), size, std::ref(flag), i, numTh, val, std::ref(completed)));
}
while (completed < numTh) {}
if (flag) {
break;
} else {
completed = 0;
val--;
}
}
cout << val << "\n";
return 0;
}