在循环c ++中重用线程

时间:2014-10-22 20:39:08

标签: c++ multithreading loops reusability

我需要在C ++程序中并行化一些任务,并且对并行编程来说是全新的。到目前为止,我通过互联网搜索取得了一些进展,但现在有点卡住了。我想在循环中重用一些线程,但显然不知道如何做我正在尝试的东西。

我从计算机上的两张ADC卡(并行获取)中获取数据,然后我需要对收集的数据(并行处理)执行一些操作,同时收集下一批数据。这是一些伪代码来说明

//Acquire some data, wait for all the data to be acquired before proceeding
std::thread acq1(AcquireData, boardHandle1, memoryAddress1a);
std::thread acq2(AcquireData, boardHandle2, memoryAddress2a);
acq1.join();
acq2.join();

while(user doesn't interrupt)
{

//Process first batch of data while acquiring new data
std::thread proc1(ProcessData,memoryAddress1a);
std::thread proc2(ProcessData,memoryAddress2a);
acq1(AcquireData, boardHandle1, memoryAddress1b);
acq2(AcquireData, boardHandle2, memoryAddress2b);
acq1.join();
acq2.join();
proc1.join();
proc2.join();
/*Proceed in this manner, alternating which memory address 
is written to and being processed until the user interrupts the program.*/
}

这是它的主要要点。循环的下一次运行将写入“a”内存地址,同时处理“b”数据并继续交替(我可以获取代码来执行此操作,只是将其取出以防止混乱问题)。

无论如何,问题(我确信有些人已经知道)是我第二次尝试使用acq1和acq2时,编译器(VS2012)说“IntelliSense:调用类类型的对象而没有适当的operator()或转换函数到指针到函数类型“。同样,如果我将std :: thread再次放在acq1和acq2之前,它会显示“错误C2374:'acq1':重新定义;多次初始化”。

所以问题是,在完成上一个任务后,我可以将线程重新分配给新任务吗?我总是等待先前使用线程结束再次调用它,但我不知道如何重新分配线程,因为它在循环中,我不能每次都创建一个新线程(或者如果我可能,这似乎是浪费和不必要的,但我可能会弄错。)

提前致谢

6 个答案:

答案 0 :(得分:23)

最简单的方法是使用std::function个对象的可等待队列。像这样:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <functional>
#include <chrono>


class ThreadPool
{
    public:

    ThreadPool (int threads) : shutdown_ (false)
    {
        // Create the specified number of threads
        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();
    }

    protected:

    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 ();
        }

    }

    std::mutex lock_;
    std::condition_variable condVar_;
    bool shutdown_;
    std::queue <std::function <void (void)>> jobs_;
    std::vector <std::thread> threads_;
};

void silly (int n)
{
    // A silly job for demonstration purposes
    std::cerr << "Sleeping for " << n << " seconds" << std::endl;
    std::this_thread::sleep_for (std::chrono::seconds (n));
}

int main()
{
    // Create two threads
    ThreadPool p (2);

    // Assign them 4 jobs
    p.doJob (std::bind (silly, 1));
    p.doJob (std::bind (silly, 2));
    p.doJob (std::bind (silly, 3));
    p.doJob (std::bind (silly, 4));
}

答案 1 :(得分:9)

std::thread类旨在执行一个任务(在构造函数中给出的任务),然后结束。如果你想做更多的工作,你需要一个新的线程。从C ++ 11开始,这就是我们所拥有的。线程池没有成为标准。 (我不确定C ++ 14对它们的看法。)

幸运的是,您可以自己轻松实现所需的逻辑。这是大型图片:

  • 启动所有执行以下操作的 n 工作线程:
    • 重复,还有更多工作要做:
      • 抓住下一个任务 t (可能要等到一个准备就绪)。
      • 处理 t
  • 继续在处理队列中插入新任务。
  • 告诉工作人员线程没有其他事可做。
  • 等待工作线程完成。

这里最困难的部分(仍然相当容易)是正确设计工作队列。通常,同步链表(来自STL)将为此做。同步意味着任何希望操纵队列的线程只有在获得std::mutex之后才能这样做,以避免竞争条件。如果工作线程发现列表为空,则必须等到再次有一些工作。您可以使用std::condition_variable。每次将新任务插入队列时,插入线程会通知一个等待条件变量的线程,因此将停止阻塞并最终开始处理新任务。

第二个不那么重要的部分是如何向工作线程发出信号,表明没有更多的工作要做。显然,你可以设置一些全局标志,但是如果一个工人被阻塞在队列中等待,它将很快就不会实现。一种解决方案可能是notify_all()个线程,并让它们在每次通知时检查标志。另一种选择是在队列中插入一些不同的“有毒”项目。如果一个工人遇到这个项目,它就会自行退出。

使用自定义的task对象或简单地使用lambdas来表示任务队列是直截了当的。

以上所有都是C ++ 11的功能。如果您遇到早期版本,则需要使用为您的特定平台提供多线程的第三方库。

虽然这些都不是火箭科学,但第一次仍然容易出错。不幸的是,与并发相关的错误是最难调试的。通过花几个小时阅读一本好书的相关部分或完成一个教程,可以很快得到回报。

答案 2 :(得分:0)

 std::thread acq1(...)

是构造函数的调用。构造一个名为acq1

的新对象

  acq1(...)

是()运算符在现有对象aqc1上的应用。如果没有为std :: thread定义这样的运算符,编译器会抱怨。

据我所知,你可能不会重用std :: threads。你构建并启动它们。和他们一起扔掉他们,

答案 3 :(得分:0)

嗯,这取决于你是否考虑转移重新分配。您可以移动线程但不能复制它。

下面的代码将在每次迭代时创建新的线程对,并将它们移动到旧线程的位置。我认为这应该有效,因为新的thread对象将成为临时对象。

while(user doesn't interrupt)
{
//Process first batch of data while acquiring new data
std::thread proc1(ProcessData,memoryAddress1a);
std::thread proc2(ProcessData,memoryAddress2a);
acq1 = std::thread(AcquireData, boardHandle1, memoryAddress1b);
acq2 = std::thread(AcquireData, boardHandle2, memoryAddress2b);
acq1.join();
acq2.join();
proc1.join();
proc2.join();
/*Proceed in this manner, alternating which memory address 
is written to and being processed until the user interrupts the program.*/
}

进行的是,对象实际上并没有在迭代结束时终止它的生命周期,因为它在外部作用域中声明了循环。但每次都会创建一个新对象并发生move。我不知道什么可以幸免(我可能是愚蠢的),所以我想这就像在循环中声明acq并简单地重复使用符号一样。总而言之......是的,它是关于如何对创建临时和move进行分类。

此外,这显然会在每个循环中启动一个新线程(当然结束先前分配的线程),它不会使线程等待新数据并神奇地将其提供给处理管道。您需要以不同的方式实现它。例如:工作线程池和队列通信。

参考文献:operator=(ctor)

我认为你得到的错误是不言自明的,所以我会跳过解释。

答案 4 :(得分:-1)

我认为你需要一个更简单的答案,不止一次运行一组线程,这是最好的解决方案:

do{

    std::vector<std::thread> thread_vector;

     for (int i=0;i<nworkers;i++)
     {
       thread_vector.push_back(std::thread(yourFunction,Parameter1,Parameter2, ...));
    }

    for(std::thread& it: thread_vector)
    { 
      it.join();
    }
   q++;
} while(q<NTIMES);

答案 5 :(得分:-2)

您也可以创建自己的Thread类并调用其运行方法,如:

class MyThread
{
public:
void run(std::function<void()> func) {
   thread_ = std::thread(func);
}
void join() {
   if(thread_.joinable())
      thread_.join();
}
private:
   std::thread thread_;
};

// Application code...
MyThread myThread;
myThread.run(AcquireData);