3

条件变量使用互斥体,.wait() 函数解锁互斥体,以便另一个线程可以访问共享数据。当通知条件变量时,它会尝试再次锁定互斥锁以使用共享数据。

此模式用于Anthony Williams的以下 concurrent_queue 示例:

template<typename Data>
class concurrent_queue
{
private:
    boost::condition_variable the_condition_variable;
public:
    void wait_for_data()
    {
        boost::mutex::scoped_lock lock(the_mutex);
        while(the_queue.empty())
        {
            the_condition_variable.wait(lock);
        }
    }
    void push(Data const& data)
    {
        boost::mutex::scoped_lock lock(the_mutex);
        bool const was_empty=the_queue.empty();
        the_queue.push(data);
        if(was_empty)
        {
            the_condition_variable.notify_one();
        }
    }
};

由于代码使用 std::queue ,因此很明显在访问队列时必须锁定互斥锁。但是,假设使用PPL 中的Microsoft 的 Concurrency::concurrent_queue而不是 std::queue。empty、push 和 try_pop 等成员函数是线程安全的。在这种情况下我是否仍然需要锁定互斥锁,或者可以像这样使用条件变量,而不会产生任何可能的竞争条件。

我的代码(这似乎有效,但这在多线程中意味着什么?)看起来像这样。我有一个将项目推送到 Microsoft 的 concurrent_queue 的生产者和一个等待此队列中的新项目的后台线程。

消费者/后台线程:

while(runFlag) //atomic
{
    while(the_queue.empty() && runFlag) //wait only when thread should still run
    {
        boost::mutex mtx; //local mutex thats locked afterwards. awkward.
        boost::mutex::scoped_lock lock(mtx);
        condition.wait(lock);
    }

    Data d;
    while(!the_queue.empty() && the_queue.try_pop(d))
    {
       //process data
    }
}

生产者/主线程:

const bool was_empty = the_queue.empty();
Data d; 
the_queue.push(d);
if(was_empty) cond_var.notify_one();

关机程序:

bool expected_run_state = true;
if(runLoop.compare_exchange_strong(expected_run_state, false))
{
    //atomically set our loop flag to false and 
    //notify all clients of the queue to wake up and exit
    cond_var.notify_all();
}

如上所述,这段代码似乎有效,但这并不一定意味着它是正确的。尤其是仅因为条件变量接口迫使我使用互斥锁而使用的本地互斥锁,这似乎是一个非常糟糕的主意。我想使用条件变量,因为添加到队列中的数据项之间的时间很难预测,我必须像这样定期创建睡眠和唤醒:

if(the_queue.empty()) Sleep(short_amount_of_time);

是否有任何其他的,也许是操作系统(在我的情况下:Windows)特定的工具,使后台线程休眠直到满足某些条件而没有定期唤醒并检查条件?

4

2 回答 2

1

是否有任何其他(可能是操作系统(在我的情况下:Windows)特定工具)使后台线程休眠直到满足某些条件而没有定期唤醒并检查条件?

这正是Events的用途

但是如果您只针对 Windows 平台 (Vista+),您应该查看
Slim Reader/Writer (SRW) Locks

于 2013-06-10T11:31:46.230 回答
1

例如,代码在不同的场景中是不正确的。如果队列在const bool was_empty = the_queue.empty();被评估时只有一个元素,但一个线程消耗了该元素并且另一个线程尝试消耗并等待条件,则写入器在将元素插入队列后不会通知该线程。

关键问题是接口中的所有操作都是线程安全的这一事实并不一定意味着您对接口的使用是安全的。如果您依赖于原子执行的多个操作,则需要在外部提供同步机制。

于 2013-06-10T10:45:09.500 回答