Files
concurrency-in-action/ch7/stack.cpp
2018-10-18 00:40:34 -05:00

168 lines
4.6 KiB
C++

// Listing 7.4
// The reference-counted reclamation machinery
// This works reasonably well in low-load situations, where there are sutiable
// quiescent points at which no threads are in pop(). In high-load situaions,
// there may never be such a quiescent state. Under such a scenario, the
// to_be_deleted list would grow without bounds, and you'd be essentailly
// leaking memory again. If there aren't going to be any quiescent periods,
// you need to find an alternative mechanism for reclaming the nodes. The key
// is to identify when no more threads are accesing a particular node so that
// it can reclaimed. By far the easiest such mechanism to reason about is the
// use of hazard ponters.
#include <atomic>
#include <cstdio>
#include <iostream>
#include <memory>
#include <thread>
template <typename T>
class stack {
private:
struct node {
std::shared_ptr<T> data;
node* next;
node(const T& data_) : data(std::make_shared<T>(data_)) {}
};
std::atomic<node*> head;
std::atomic<unsigned> threads_in_pop;
std::atomic<node*> to_be_deleted;
static void delete_nodes(node* nodes)
{
while (nodes) {
node* next = nodes->next;
delete nodes;
nodes = next;
}
}
void try_reclaim(node* old_head)
{
if (threads_in_pop == 1) {
// claim list of to-be-deleted nodes
node* nodes_to_delete = to_be_deleted.exchange(nullptr);
// are you the only thread in pop()?
if (!--threads_in_pop) {
// on other thread can be accessing this list of pending nodes.
// There may be new pending nodes, but you're not bothered
// about them for now, as long as it's safe to reclaim your
// list.
delete_nodes(nodes_to_delete);
}
else if (nodes_to_delete) {
// not safe to reclaim the nodes, so if there are any,
// you must chain them back onto the list of nodes
// pending deletion.
// This can happen if there are multiple threads accessing the
// data structure concurrently. Other threads might have
// called pop() in between the first tet of thread_in_pop and
// the "claiming" of the list, potentially adding new nodes to
// the list that are still being accesed by one or more of
// those other threads.
chain_pending_nodes(nodes_to_delete);
}
delete old_head;
}
else {
// not safe to delete any nodes, add the node to the pending list
chain_pending_node(old_head);
--threads_in_pop;
}
}
void chain_pending_nodes(node* nodes)
{
node* last = nodes;
// traverse the chain to find the end
while (node* const next = last->next) {
last = next;
}
chain_pending_nodes(nodes, last);
}
void chain_pending_nodes(node* first, node* last)
{
// replace the next pointer from the last node with
// the current to_be_deleted pointer
last->next = to_be_deleted;
// store the first node in the chain as the new to_be_deleted pointer
// have to use compare_exchange_weak in a loop here in order to ensure
// that you don't leak any nodes that have been added by another thread
while (!to_be_deleted.compare_exchange_weak(last->next, first))
;
}
void chain_pending_node(node* n)
{
// adding a single node onto the list is a special case where the
// first node onto the list is a special case where the first node
// in the chain to be added is the same as the last one.
chain_pending_nodes(n, n);
}
public:
stack() : head(nullptr), threads_in_pop(0), to_be_deleted(nullptr) {}
void push(const T& data)
{
node* const new_node = new node(data);
new_node->next = head.load();
// loop to gurantee that last->next is correct
while (!head.compare_exchange_weak(new_node->next, new_node))
;
}
std::shared_ptr<T> pop()
{
++threads_in_pop; // increase counter before doing anything else
node* old_head = head.load();
while (old_head && !head.compare_exchange_weak(old_head, old_head->next))
;
std::shared_ptr<T> res;
if (old_head) {
res.swap(old_head->data); // extract data from node rather than
// coping pointer
}
try_reclaim(old_head); // reclaim deleted nodes if you can
return res;
}
};
void
push(stack<int>* s)
{
for (int i = 0; i < 10; ++i) {
printf("pushing %d\n", i);
s->push(i);
}
}
void
pop(stack<int>* s)
{
int count = 0;
std::shared_ptr<int> e;
while (count < 10) {
if (e = s->pop()) {
printf("popping %d\n", *e);
++count;
}
}
}
int
main()
{
stack<int> s;
std::thread t1(push, &s);
std::thread t2(pop, &s);
t1.join();
t2.join();
return 0;
}