[英]Thread safe ExpiringDeque data structure in C++
I am trying to create a data structure, ExpiringDeque.我正在尝试创建一个数据结构 ExpiringDeque。 It should be somewhat similar to std::deque.它应该有点类似于 std::deque。 Let's say I need only push_back(), size() and pop_front().假设我只需要 push_back()、size() 和 pop_front()。 The data structure needs to automatically expire up to N first elements every T seconds.数据结构需要每 T 秒自动过期最多 N 个第一个元素。
This data structure needs to manage its own queue and expiration thread internally.这个数据结构需要在内部管理自己的队列和过期线程。 How do I write it in a thread safe way?如何以线程安全的方式编写它? This is an example that I came up with, does this seem reasonable?这是我想出的一个例子,这看起来合理吗? What am I missing?我错过了什么?
#include <algorithm>
#include <atomic>
#include <cassert>
#include <deque>
#include <mutex>
#include <thread>
#include <unistd.h>
#include <iostream>
template <typename T>
class ExpiringDeque {
public:
ExpiringDeque(int n, int t) : numElements_(n), interval_(t), running_(true), items_({}) {
expiringThread_ = std::thread{[&] () {
using namespace std::chrono_literals;
int waitCounter = 0;
while (true) {
if (!running_) {
return;
}
std::this_thread::sleep_for(1s);
if (waitCounter++ < interval_) {
continue;
}
std::lock_guard<std::mutex> guard(mutex_);
waitCounter = 0;
int numToErase = std::min(numElements_, static_cast<int>(items_.size()));
std::cout << "Erasing " << numToErase << " elements\n";
items_.erase(items_.begin(), items_.begin() + numToErase);
}
}};
}
~ExpiringDeque() {
running_ = false;
expiringThread_.join();
}
T pop_front() {
if (items_.size() == 0) {
throw std::out_of_range("Empty deque");
}
std::lock_guard<std::mutex> guard(mutex_);
T item = items_.front();
items_.pop_front();
return item;
}
int size() {
std::lock_guard<std::mutex> guard(mutex_);
return items_.size();
}
void push_back(T item) {
std::lock_guard<std::mutex> guard(mutex_);
items_.push_back(item);
}
private:
int numElements_;
int interval_;
std::atomic<bool> running_;
std::thread expiringThread_;
std::mutex mutex_;
std::deque<T> items_;
};
int main() {
ExpiringDeque<int> ed(10, 3);
ed.push_back(1);
ed.push_back(2);
ed.push_back(3);
assert(ed.size() == 3);
assert(ed.pop_front() == 1);
assert(ed.size() == 2);
// wait for expiration
sleep(5);
assert(ed.size() == 0);
ed.push_back(10);
assert(ed.size() == 1);
assert(ed.pop_front() == 10);
return 0;
}
You can avoid an unnecessary wait in the destructor of ExpiringDeque
by using a condition variable.您可以通过使用条件变量来避免在ExpiringDeque
的析构函数中不必要的等待。 I would also use std::condition_variable::wait_for
with a predicate to check the running_
flag.我还将使用带有谓词的std::condition_variable::wait_for
来检查running_
标志。 This will ensure that you either wait for a timeout or a notification, whichever is earlier.这将确保您等待超时或通知,以较早者为准。 You avoid using waitCounter
and continue
this way.您避免使用waitCounter
并continue
这种方式。
Another thing you should do is lock the mutex before checking the size of your deque in pop_front()
, otherwise it's not thread safe.您应该做的另一件事是在pop_front()
检查双端队列的大小之前锁定互斥锁,否则它不是线程安全的。
Here's an updated version of your code:这是您的代码的更新版本:
template <typename T>
class ExpiringDeque {
public:
ExpiringDeque(int n, int t) : numElements_(n), interval_(t), running_(true), items_({}), cv_() {
expiringThread_ = std::thread{ [&]() {
using namespace std::chrono_literals;
while (true) {
//Wait for timeout or notification
std::unique_lock<std::mutex> lk(mutex_);
cv_.wait_for(lk, interval_ * 1s, [&] { return !running_; });
if (!running_)
return;
//Mutex is locked already - no need to lock again
int numToErase = std::min(numElements_, static_cast<int>(items_.size()));
std::cout << "Erasing " << numToErase << " elements\n";
items_.erase(items_.begin(), items_.begin() + numToErase);
}
} };
}
~ExpiringDeque() {
//Set flag and notify worker thread
{
std::lock_guard<std::mutex> lk(mutex_);
running_ = false;
}
cv_.notify_one();
expiringThread_.join();
}
T pop_front() {
std::lock_guard<std::mutex> guard(mutex_);
if (items_.size() == 0) {
throw std::out_of_range("Empty deque");
}
T item = items_.front();
items_.pop_front();
return item;
}
...
private:
int numElements_;
int interval_;
bool running_;
std::thread expiringThread_;
std::mutex mutex_;
std::deque<T> items_;
std::condition_variable cv_;
};
You can make the running_
flag a normal bool
since the std::condition_variable::wait_for
atomically checks for the timeout or notification.您可以将running_
标志设为普通bool
,因为std::condition_variable::wait_for
会自动检查超时或通知。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.