背景是Nested shared_ptr destruction causes stack overflow的问题。总而言之,对于看起来像这样的数据结构:
// A node in a singly linked list.
struct Node {
int head;
std::shared_ptr<Node> tail;
};
如果列表增长太长,由于Node
的递归破坏,shared_ptr
的析构函数中可能会发生堆栈溢出。我正在实现https://stackoverflow.com/a/36635668/3234803中提出的删除引擎的线程安全版本。
涉及三个简单的课程:SpinLock
,ConcurrentQueue
和DeleteEngine
。
SpinLock
类使用std::atomic_flag
:
class SpinLock {
public:
void lock()
{ while (lock_.test_and_set(std::memory_order_acquire) {} }
bool try_lock() // Not camel-case to be compatible with C++ Lockable interface
{ return !lock_.test_and_set(std::memory_order_acquire); }
void unlock()
{ lock_.clear(std::memory_order_release); }
private:
std::atomic_flag lock_ = ATOMIC_FLAG_INIT;
};
ConcurrentQueue
类实现了一个支持线程安全的enqueue
和tryDequeue
的多生产者单用户队列:
template<typename T>
class ConcurrentQueue {
public:
void enqueue(T item)
{
std::lock_guard<SpinLock> lk(lock_);
queue_.emplace_back(std::move(item));
}
bool tryDequeue(T &item)
{
std::lock_guard<SpinLock> lk(lock_);
if (queue_.empty()) {
return false;
}
item = std::move(queue_.front());
queue_.pop_front();
return true;
}
private:
std::deque<T> queue_;
SpinLock lock_;
};
DeleteEngine
类实现上述答案中提出的删除引擎:
template<typename T>
class DeleteEngine {
public:
~DeleteEngine()
{
std::lock_guard<SpinLock> lk(deleting_);
deleteAll();
}
void enqueue(T *p)
{
queue_.enqueue(p);
if (deleting_.try_lock()) {
std::lock_guard<SpinLock> lk(deleting_, std::adopt_lock);
deleteAll();
}
}
private:
void deleteAll()
{
T *p = nullptr;
while (queue_.tryDequeue(p)) {
delete p;
}
}
ConcurrentQueue<T *> queue_;
SpinLock deleting_;
};
现在在shared_ptr<Node>
的删除中,我们会将原始指针移交给DeleteEngine<Node>
,而不是直接delete
。递归delete
最多可以处理大约10000个节点,而此方法可以处理任意数量的节点。
上面的代码只是一个原型,但我特别关注这个实现的性能:(1)大多数时候Node
类将在单线程环境中使用; (2)偶尔它可能用于高度并发的应用程序,例如一个不断创建和销毁对象的Web服务器。
这种实现明显慢于天真的递归破坏(64个线程的微基准测试和每个线程10000个节点显示大约10倍的减速)。我该怎么做才能改善它?