与boost :: condition_variable进行线程同步

时间:2010-09-22 08:47:03

标签: c++ boost threadpool

我正在做一些关于C ++多线程的实验,我不知道如何解决一个问题。假设我们有线程池,当没有可用的免费线程时,它使用现有线程处理用户请求并创建新线程。我创建了command_queue线程安全类,它有push和pop方法。队列为空时弹出等待,仅在命令可用或超时时返回。现在是时候实现线程池了。这个想法是让自由线程睡眠一段时间,如果在那段时间后没有任何事情要做,就会杀死线程。这是实现

command_queue::handler_t handler;
while (handler = tasks.pop(timeout))
{
    handler();
}

这里我们退出线程过程,如果发生超时。这很好,但新线程创建存在问题。假设我们已经有2个线程处理用户请求,他们现在正在工作,但我们需要异步执行其他操作。 我们打电话给

thread_pool::start(some_operation);

应该启动新线程,因为没有可用的空闲线程。当线程可用时,它会在条件变量上调用timed_wait,因此我们的想法是检查是否有等待的线程。

if (thread_are_free_threads) // ???
   condition.notify_one();
else
   create_thread(thread_proc);

但是怎么检查呢?文档说,如果没有等待线程,notify_one什么都不做。如果我能检查它是否没有做任何解决方案

if (!condition.notify_one()) // nobody was notified
   create_thread(thread_proc);

据我所知,没有办法检查。

感谢您的回答。

5 个答案:

答案 0 :(得分:2)

你需要创建另一个变量(也许是一个信号量)来知道正在运行多少线程,然后你可以在调用notify之前检查并创建一个新线程(如果需要)。

另一个更好的选择是在超时时不让你的线程退出。他们应该活着等待通知。当通知超时时,不要退出,检查变量以查看程序是否仍在运行或是否“正在关闭”,如果它仍在运行,则再次开始等待。

答案 1 :(得分:1)

更典型的线程池看起来像这样:

Pool::Pool()
{
    runningThreads = 0;
    actualThreads  = 0;
    finished       = false;
    jobQue.Init();

    mutex.Init();
    conditionVariable.Init();

    for(int loop=0; loop < threadCount; ++loop) { startThread(threadroutine); }
}

Pool::threadroutine()
{

    {
        // Extra code to count threads sp we can add more if required.
        RAIILocker doLock(mutex);
        ++ actualThreads;
        ++ runningThreads;
    }
    while(!finished)
    {
         Job job;
         {
             RAIILocker doLock(mutex);

             while(jobQue.empty())
             {
                 // This is the key.
                 // Here the thread is suspended (using zero resources)
                 // until some other thread calls the notify_one on the
                 // conditionVariable. At this point exactly one thread is release
                 // and it will start executing as soon as it re-acquires the lock
                 // on the mutex.
                 //
                 -- runningThreads;
                 conditionVariable.wait(mutex);
                 ++ runningThreads;
             }
             job = jobQue.getJobAndRemoveFromQue();
         }
         job.execute();
    }
    {
        // Extra code to count threads sp we can add more if required.
        RAIILocker doLock(mutex);
        -- actualThreads;
        -- runningThreads;
    }
}

Pool::AddJob(Job job)
{
    RAIILocker doLock(mutex);

    // This is where you would check to see if you need more threads.
    if (runningThreads == actualThreads) // Plus some other conditions.
    {
        // increment both counts. When it waits we decrease the running count.
        startThread(threadroutine);
    }
    jobQue.push_back(job);
    conditionVariable.notify_one();  // This releases one worker thread
                                     // from the call to wait() above.
                                     // Note: The worker thread will not start
                                     //       until this thread releases the mutex.
}

答案 2 :(得分:0)

我认为您需要重新考虑您的设计。在发牌人线程的简单模型中,发牌人将作业线程分开,经销商将作业放到消息队列中,让其中一个玩家在有机会时接受作业。

在您的情况下,经销商正在积极管理线程池,因为它保留了哪些玩家线程处于空闲状态以及哪些线程处于忙碌状态的知识。由于经销商知道哪个玩家空闲,经销商可以主动通过闲置工作并使用简单的信号量(或cond var)向玩家发出信号 - 每个玩家有一个信号量。在这种情况下,经销商通过给线程 kill self 工作来主动销毁空闲线程可能是有意义的。

答案 3 :(得分:0)

现在我找到了一个解决方案,但并不是那么完美。 我有一个名为free的volatile成员变量 - 它存储池中的空闲线程数。

void thread_pool::thread_function()
{
    free++;
    command_queue::handler_t handler;
    while (handler = tasks.pop(timeout))
    {
        free--;
        handler();
        free++;
    }
    free--;
}

当我将任务分配给线程时,我会做这样的事情

if (free == 0)
    threads.create_thread(boost::bind(&thread_pool::thread_function, this));

同步仍然存在问题,因为如果在free thread_function之后将切换上下文,我们可能会创建一个我们实际上不需要的新线程,但是因为任务队列是线程安全的没有问题,这只是一个不必要的开销。你能建议解决这个问题吗?你怎么看待它?也许最好留下它,因为它在这里再进行一次同步?

答案 4 :(得分:0)

另一个想法。您可以查询消息队列的长度。如果它太长,请创建一个新工人。