仅使用互斥保护每个成员函数
问题 1
尽管运用互斥保护共享数据,条件竞争仍然无法避免,考虑如下代码:
if (!q.empty()) {
const int value = q.front();
q.pop();
do_something(value);
}
在 empty() 和 front() 之间可能有另一个线程调用 pop(),导致 front() 空队列调用
解决办法
front() 空队列调用抛出异常
缺点
- 使用起来麻烦,需要将整个 if 语句置于 try 块中
- 如果队列本身为空,调用 front() 肯定抛出异常,if 语句成了优化手段,而非必要设计
问题 2
A | B |
---|---|
if (!q.empty()) | |
if (!q.empty()) | |
const int value = q.front() | |
const int value = q.front() | |
q.pop() | |
do_something(value) | q.pop() |
do_something(value) |
队首元素被读取两次,但队首的第二个元素始终未被读取就被丢弃
解决办法
把 front() 和 pop() 组成一个成员函数
缺点
为了安全,常修改 front() 的实现使其返回副本而非引用。考虑 queue<vector< int>>,当把 front() 和 pop() 组成一个成员函数时,先调用 front(),假如系统负载过重或内存资源严重受限,内存分配可能失败,导致 vector 的拷贝构造函数抛出异常,但是 pop() 调用会使元素从栈上移除从而造成数据丢失。原接口的设计者将操作一分为二,即使无法安全地复制数据,数据还是留存在栈上。
最终解决办法
- 传入引用 / 返回指向弹出元素的指针
- 提供不抛出异常的拷贝构造函数或不抛出异常的移动构造函数
注意
代码可使用 std::move() 进行优化
无限队列代码
#include <queue>
#include <mutex>
#include <condition_variable>
template <typename T>
class threadsafe_queue
{
private:
mutable std::mutex mut;
std::condition_variable data_cond;
std::queue<T> data_queue;
public:
threadsafe_queue() {}
void push(T new_value)
{
std::lock_guard<std::mutex> lk(mut);
data_queue.push(new_value);
data_cond.notify_one();
}
bool try_pop(T &value)
{
std::lock_guard<std::mutex> lk(mut);
if (data_queue.empty())
return false;
value = data_queue.front();
data_queue.pop();
return true;
}
std::shared_ptr<T> try_pop()
{
std::lock_guard<std::mutex> lk(mut);
if (data_queue.empty())
return false;
std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
data_queue.pop();
return res;
}
void wait_and_pop(T &value)
{
std::unique_lock<std::mutex> lk(mut);
data_cond.wait(lk, [this]
{ return !data_queue.empty(); });
value = 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>(data_queue.front()));
data_queue.pop();
return res;
}
bool empty() const
{
std::lock_guard<std::mutex> lk(mut);
return data_queue.empty();
}
};
有限队列代码
#include <queue>
#include <mutex>
#include <condition_variable>
template <typename T>
class threadsafe_queue
{
private:
mutable std::mutex mut;
std::condition_variable not_empty, not_full; // 变量名指示线程不阻塞的条件
size_t max_sz;
std::queue<T> data_queue;
public:
threadsafe_queue(size_t sz) : max_sz(sz) {}
bool try_push(T new_value)
{
std::lock_guard<std::mutex> lk(mut);
if (data_queue.size() >= max_sz) // 最好底层数据结构有 full 函数,会看起来更统一
return false;
data_queue.push(new_value);
not_empty.notify_one();
return true;
}
void wait_and_push(T new_value)
{
std::unique_lock<std::mutex> lk(mut);
not_full.wait(lk, [this]
{ return data_queue.size() < max_sz; });
data_queue.push(new_value);
not_empty.notify_one();
}
bool try_pop(T &value)
{
std::lock_guard<std::mutex> lk(mut);
if (data_queue.empty())
return false;
value = data_queue.front();
data_queue.pop();
not_full.notify_one();
return true;
}
std::shared_ptr<T> try_pop()
{
std::lock_guard<std::mutex> lk(mut);
if (data_queue.empty())
return false;
std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
data_queue.pop();
not_full.notify_one();
return res;
}
void wait_and_pop(T &value)
{
std::unique_lock<std::mutex> lk(mut);
not_empty.wait(lk, [this]
{ return !data_queue.empty(); });
value = data_queue.front();
data_queue.pop();
not_full.notify_one();
}
std::shared_ptr<T> wait_and_pop()
{
std::unique_lock<std::mutex> lk(mut);
not_empty.wait(lk, [this]
{ return !data_queue.empty(); });
std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
data_queue.pop();
not_full.notify_one();
return res;
}
bool empty() const
{
std::lock_guard<std::mutex> lk(mut);
return data_queue.empty();
}
};