C ++重用调用相同函数的线程向量

时间:2017-11-08 03:28:26

标签: c++ multithreading c++14 threadpool stdthread

我想重用一个使用不同参数多次调用相同函数的线程向量。没有写入(原子参数除外),因此不需要互斥锁。为了描述这个想法,我创建了一个并行化代码的基本示例,该代码可以找到向量的最大值。有明显更好的方法来找到向量的最大值,但为了解释并避免深入了解我正在编写的实际代码的详细信息,我将使用这个愚蠢的例子。

代码通过调用函数 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 |

3 个答案:

答案 0 :(得分:1)

构造后无法为std::thread分配不同的执行函数(闭包)。这通常适用于所有线程抽象,尽管通常实现会尝试在内部进行memoize或缓存低级抽象,以使线程分叉并快速加入,因此只需构建新线程是可行的。系统编程圈中存在一个争论,即创建一个新线程是否应该非常轻量级,或者是否应该经常将客户端写入fork线程。 (鉴于这种情况已经持续了很长时间,应该很明显涉及到许多权衡因素。)

还有很多其他抽象尝试做你真正想要的事情。它们具有诸如“线程池”,“任务执行器”(或仅“执行器”)和“期货”之类的名称。所有这些都倾向于通过创建一些线程映射到线程上,这些线程通常与系统中的硬件核心数相关,然后让每个线程循环并查找请求。

正如评论所指出的那样,你自己做的主要方法是让线程有一个接受执行请求,处理它们然后发布结果的顶级循环。为此,您需要使用其他同步方法,如互斥锁和条件变量。如果有很多请求并且请求不是非常大,那么以这种方式执行通常会更快。

尽管标准C ++并发支持是一件好事,但它对于真实世界的高性能工作也相当缺乏。像Intel's TBB这样的东西更像是一种工业强度解决方案。

答案 1 :(得分:0)

您的代码存在竞争条件:bool 是原子类型,因此可以安全地让多个线程同时写入。您需要使用std::atomic_boolstd::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;
}