在线程在C ++中完成后,为线程分配新任务

时间:2016-03-06 13:31:57

标签: c++ multithreading concurrency stdthread

我在C ++中有以下代码。代码来自C++ Concurrency In Action: Practical Multithreading

void do_work(unsigned id);

void f() {
    std::vector<std::thread> threads;
    for(unsigned i = 0; i < 20; ++i) {
        threads.push_back(std::thread(do_work, i));
    }
    std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join));
}

假设线程[0]已完成处理并返回一个值。我还有更多要处理的文件,现在想将这个新文件分配给一个完整的线程。如何在C ++中实现此行为?或者我必须销毁线程,现在在线程完成时创建一个新线程?但是,我该如何检查这些线程是否已完成?

2 个答案:

答案 0 :(得分:3)

这是Sam Varshavchik所解释的基本实现。

<强> Live demo

我添加local_queue的原因是为了确保我们的m_Mutex立即解锁。如果删除它,则调用push_task的线程可能会阻塞。

析构函数调用stop()m_Running设置为false,通知线程有关它,并等待它完成所有剩余任务的处理。

如果工人类死了,线程也会死掉,这很好。

我的示例仅为每个线程for (int i = 0; i < 5; i++)创建3个线程和5个任务,主要是为了确保所有输出都以 ideone 显示,但我已经用10个线程对其进行了测试每个线程有5000个任务,运行正常。

如果希望正确同步输出流,do_work函数有两行可以取消注释。 此类具有多线程支持。

您可以根据需要stop()和重新start()线程

class Worker
{
public:
    Worker(bool start) : m_Running(start) { if (start) private_start(); }
    Worker() : m_Running(false) { }
    ~Worker() { stop(); }

    template<typename... Args>
    void push_task(Args&&... args)
    {
        {
            std::lock_guard<std::mutex> lk(m_Mutex);
            m_Queue.push_back(std::bind(std::forward<Args>(args)...));
        }

        m_Condition.notify_all();
    }

    void start()
    {
        {
            std::lock_guard<std::mutex> lk(m_Mutex);
            if (m_Running == true) return;
            m_Running = true;
        }

        private_start();
    }

    void stop()
    {
        {
            std::lock_guard<std::mutex> lk(m_Mutex);
            if (m_Running == false) return;
            m_Running = false;
        }

        m_Condition.notify_all();
        m_Thread.join();
    }

private:
    void private_start()
    {
        m_Thread = std::thread([this]
        {
            for (;;)
            {
                decltype(m_Queue) local_queue;
                {
                    std::unique_lock<std::mutex> lk(m_Mutex);
                    m_Condition.wait(lk, [&] { return !m_Queue.empty() + !m_Running; });

                    if (!m_Running)
                    {
                        for (auto& func : m_Queue)
                            func();

                        m_Queue.clear();
                        return;
                    }

                    std::swap(m_Queue, local_queue);
                }

                for (auto& func : local_queue)
                    func();
            }
        });
    }

private:
    std::condition_variable m_Condition;
    std::list<std::function<void()>> m_Queue;
    std::mutex m_Mutex;
    std::thread m_Thread;
    bool m_Running = false;
};

void do_work(unsigned id)
{
    //static std::mutex cout_mutex;
    //std::lock_guard<std::mutex> lk(cout_mutex);
    std::cout << id << std::endl;
}

int main()
{
    {
        Worker workers[3];
        int counter = 0;

        for (auto& worker : workers)
            worker.start();

        for (auto& worker : workers)
        {
            for (int i = 0; i < 5; i++)
                worker.push_task(do_work, ++counter + i);
        }
    }

    std::cout << "finish" << std::endl;
    getchar();

    return 0;
}

答案 1 :(得分:2)

简短回答&#34;我如何在C ++中实现这种行为&#34;简单地说就是编写代码来完成它。您自己确定的第一步是&#34;如何检查这些线程是否已完成&#34;。

有几种基本方法。但它们都归结为同样的事情:在每个线程终止之前,它不会让每个线程都消失,而是通知父进程它已完成。

首先,每个线程应该知道它是哪个线程。在您的示例中,所有线程都放在std::vector中,并且它们由向量索引标识。这不是唯一的方法。还有其他方法来管理所有线程,但为了答案的目的,这样做。

然后,每个线程需要通过将线程索引号作为线程参数传递来知道它是什么索引。你的代码已经做了哪些。精彩。

现在,只需关闭循环结束:您只需要使用std::mutex实例化std::condition_variable,即可保护std::queue或{ {1}}。或者,也许是std::list整数。您可以自由决定哪种容器最适合您。

然后,在每个线程终止之前,它:

  • 锁定互斥锁。

  • 将其线程索引放入容器中。

  • 表示条件变量。

  • 解锁互斥锁,然后立即返回,终止此线程。

然后,父线程启动了所有线程:

  • 锁定互斥锁

  • 检查队列/ set / whatever是否为空。如果是,则等待条件变量,直到它不是。

  • 从queue / set / whatever中删除线程索引,并加入该线程。该线程刚刚终止。现在您知道哪个线程已终止,并且可以使用该信息执行任何操作。

  • 完成处理或重新启动线程后,如果队列为空,则再次检查。