当从多个线程访问对象时,一次访问可能会更改对象,您需要一些同步。对于所描述的场景,您可能希望有一个队列类来执行必要的线程保护和信令。这是一个简单的实现:
#include <mutex>
#include <condition_variable>
#include <deque>
template <typename T>
class queue
{
private:
std::mutex d_mutex;
std::condition_variable d_condition;
std::deque<T> d_queue;
public:
void push(T const& value) {
{
std::unique_lock<std::mutex> lock(this->d_mutex);
d_queue.push_front(value);
}
this->d_condition.notify_one();
}
T pop() {
std::unique_lock<std::mutex> lock(this->d_mutex);
this->d_condition.wait(lock, [=]{ return !this->d_queue.empty(); });
T rc(std::move(this->d_queue.back()));
this->d_queue.pop_back();
return rc;
}
};
该代码使用 C++ 2011 结构,但可以轻松更改以避免使用它们,而改为使用 C++ 2003 结构,除非这些结构不是标准化的。
关键点是:
- A
std::mutex
用于确保一次只有一个线程访问队列。
- 当将某些东西放入队列时,会获取互斥锁上的锁并将对象插入队列中。插入对象后,锁会自动释放,并发出条件变量信号。
- 当从队列中提取某些东西时,会获取互斥锁上的锁,并且线程使用条件变量等待队列非空:如果队列中已经有东西,则条件将
true
立即wait()
返回,否则线程进入睡眠状态,直到它收到一个信号,此时重新评估条件。请注意,条件可能会被多次评估,因为条件变量可能会被虚假唤醒。
- lambda 通过值捕获其上下文:它真正捕获的只是
this
; 成员变量不能直接被捕获,因为它们不是本地上下文的一部分。
- 结果 from
pop()
是按值返回的:在锁被释放的那一刻,容器可能会发生变化,使得无法通过引用返回对象,即使它们被放置在合适的位置。
这有点像一个玩具示例的主要原因是它没有关闭系统的好方法:如果一个线程在队列中被阻塞,它会等待直到有另一个对象。一个真正的实现会有某种方式来表明是时候关闭了,可能会从pop()
. 此外,有时使用不强制阻塞提取对象的队列很有用。相反,它会有一个try_pop()
获取锁的函数,检查队列是否为非空,并根据结果提取和对象或信号失败。不过,这样的功能很容易实现。