【问题标题】:Synchronizing tasks同步任务
【发布时间】:2014-09-22 17:59:22
【问题描述】:

我正在开发一个使用线程池的应用程序,向它提交任务并同步它们。主线程必须等到单个循环迭代中所有提交的任务完成,然后再提交另一组任务(因为下一次迭代中的任务操作相同的数据,它们将相互依赖)。

我的问题是,最好的方法是什么?

到目前为止,我想出的是,每个线程在完成一项任务后,都会增加一个原子无符号整数。当整数等于提交的任务数时,主线程继续工作并提交另一轮任务。

这是我的第一个多线程应用程序。 这是处理这个问题的最佳和明智的方法吗?

我正在使用从一本优秀的书“C++ Concurrency in Action: by Anthony Williams”中复制的线程池类。

以下是课程:

class thread_pool
{
    std::atomic_bool done;
    thread_safe_queue<std::function<void()> > work_queue;
    std::vector<std::thread> threads;
    join_threads joiner;

    void worker_thread()
    {
        while(!done)
        {
            std::function<void()> task;
            if(work_queue.try_pop(task))
            {
                task();
            }
            else
            {
                std::this_thread::yield();
            }
        }
    }
public:
    thread_pool():
        done(false),joiner(threads)
    {
        unsigned const thread_count=std::thread::hardware_concurrency();
        try
        {
            for(unsigned i=0;i<thread_count;++i)
            {
                threads.push_back(
                    std::thread(&thread_pool::worker_thread,this));
            }
        }
        catch(...)
        {
            done=true;
            throw;
        }
    }

    ~thread_pool()
    {
        done=true;
    }

    template<typename FunctionType>
    void submit(FunctionType f)
    {
        work_queue.push(std::function<void()>(f));
    }
};

template<typename T>
class threadsafe_queue
{
private:
    mutable std::mutex mut;
    std::queue<T> data_queue;
    std::condition_variable data_cond;
public:
    threadsafe_queue()
    {}

    void push(T new_value)
    {
        std::lock_guard<std::mutex> lk(mut);
        data_queue.push(std::move(new_value));
        data_cond.notify_one();
    }

    void wait_and_pop(T& value)
    {
        std::unique_lock<std::mutex> lk(mut);
        data_cond.wait(lk, [this]{return !data_queue.empty(); });
        value = std::move(data_queue.front());
        data_queue.pop();
    }

    std::shared_ptr<T> wait_and_pop()
    {
        std::unique_lock<std::mutex> lk(mut);
        data_cond.wait(lk, [this]{return !data_queue.empty(); });
        std::shared_ptr<T> res(
            std::make_shared<T>(std::move(data_queue.front())));
        data_queue.pop();
        return res;
    }

    bool try_pop(T& value)
    {
        std::lock_guard<std::mutex> lk(mut);
        if (data_queue.empty())
            return false;
        value = std::move(data_queue.front());
        data_queue.pop();
    }

    std::shared_ptr<T> try_pop()
    {
        std::lock_guard<std::mutex> lk(mut);
        if (data_queue.empty())
            return std::shared_ptr<T>();
        std::shared_ptr<T> res(
            std::make_shared<T>(std::move(data_queue.front())));
        data_queue.pop();
        return res;
    }

    bool empty() const
    {
        std::lock_guard<std::mutex> lk(mut);
        return data_queue.empty();
    }
};

main() 函数:

std::condition_variable waitForThreads;
std::mutex mut;

std::atomic<unsigned> doneCount = 0;

unsigned threadCount = 4; // sample concurrent thread count that I use for testing

 void synchronizeWork()
   {
    doneCount++;
    if (doneCount.load() == threadCount)
    {
        doneCount = 0;
        std::lock_guard<std::mutex> lock(mut);
        waitForThreads.notify_one();
    }
   }

   void Task_A()
   {
    std::cout << "Task A, thread id: " << std::this_thread::get_id() << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(3000));
    synchronizeWork();
   }

int main()
{   
    unsigned const thread_count = std::thread::hardware_concurrency();
    thread_pool threadPool;

    for (int i = 0; i < 1000; ++i)
    {
        for (unsigned j = 0; j < thread_count; j++)
            threadPool.submit(Task_A);

// Below is my way of synchronizing the tasks

        {
            std::unique_lock<std::mutex> lock(mut);
            waitForThreads.wait(lock);
        }

    }

【问题讨论】:

    标签: multithreading c++11 threadpool


    【解决方案1】:

    我不熟悉你使用的线程池类。

    不使用这样的类,通常的做法是这样的:

      std::cout << "Spawning 3 threads...\n";
      std::thread t1 (pause_thread,1);
      std::thread t2 (pause_thread,2);
      std::thread t3 (pause_thread,3);
      std::cout << "Done spawning threads. Now waiting for them to join:\n";
      t1.join();
      t2.join();
      t3.join();
      std::cout << "All threads joined!\n";
    

    我想任何像样的线程池类都可以让你做同样的事情,更简单的是,给你一个阻塞的方法,直到所有线程都完成。我建议您仔细检查文档。

    【讨论】:

    • 问题是,我不想在每次需要完成任务时都创建一个新线程,因为我认为它会效率低下(因为同类型的任务需要执行数千次次)。相反,如果队列中有任何挂起的任务,则生成的线程会循环检查,然后执行工作。
    • 如果创建线程所花费的时间很长,那么您的任务太小了。您应该将足够多的任务合二为一,这样线程开销就不会很大。
    • 完成我的任务的时间非常重要,因为他们进行分子动力学计算。但是我仍然不确定是否为每次迭代创建一个新线程是处理这个问题的最优雅的方式,这似乎太容易了。这意味着我根本不需要线程池。
    • 优雅简洁大方。复杂和坚硬不是优雅。这也称为 KISS 主体。如果您不需要线程池,那是一件好事,因为您可以少出一件事。
    • 谢谢。最后一个问题。您个人是否认为这种情况下的线程池是不必要的?在我的程序中,任务的数量将始终等于线程的数量,并且它们都执行相同的任务。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多