Skip to content

Commit d0ebbb1

Browse files
committed
Page 217
1 parent ebef795 commit d0ebbb1

1 file changed

Lines changed: 98 additions & 1 deletion

File tree

lfq_gen/lfq_gen.cpp

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ class lock_free_queue {
1919
};
2020

2121
struct node_counter {
22+
// Keeping the structure within a machine word makes it more likely
23+
// that the atomic operations can be lock-free on many platforms
2224
unsigned internal_count:30;
2325
unsigned external_counters:2; // 2
2426
};
@@ -32,18 +34,84 @@ class lock_free_queue {
3234
node() {
3335
node_counter new_count;
3436
new_count.internal_count = 0;
37+
// queue's own counters: only head and tail (max = 2)
3538
new_count.external_counters = 2; // 4
3639
count.store(new_count);
3740

3841
next.ptr = nullptr;
3942
next.external_count = 0;
4043
}
44+
45+
/**
46+
* Decreases this node's internal_count and deletes useless node */
47+
void release_ref() {
48+
node_counter old_counter = count.load(std::memory_order_relaxed);
49+
node_counter new_counter;
50+
51+
do {
52+
new_counter = old_counter;
53+
--new_counter.internal_count; // 1
54+
} while (!count.compare_exchange_strong(old_counter, new_counter, // 2
55+
std::memory_order_acquire, std::memory_order_relaxed));
56+
57+
if (!new_counter.internal_count && !new_counter.external_counters) {
58+
delete this; // 3
59+
}
60+
}
61+
62+
63+
/**
64+
* Makes both arguments equal to the first argument with external_count
65+
* incremented by 1 */
66+
static void increase_external_count(std::atomic<counted_node_ptr>& counter,
67+
counted_node_ptr& old_counter) {
68+
counted_node_ptr new_counter;
69+
70+
do {
71+
new_counter = old_counter;
72+
++new_counter.external_count;
73+
} while (!counter.compare_exchange_strong(old_counter, new_counter,
74+
std::memory_order_acquire, std::memory_order_relaxed));
75+
76+
old_counter.external_count = new_counter.external_count;
77+
};
78+
79+
/**
80+
* Decrease argument node's external count by 1, and internal count
81+
* by 2. If both counters become 0 then deletes node pointed by
82+
* argument's counted_node_ptr */
83+
static void free_external_count(counted_node_ptr& old_node_ptr) {
84+
// make a copy of arguments nodes's pointer to node
85+
node* const ptr = old_node_ptr.ptr;
86+
87+
// calculate arguments node's external count decremented by 2
88+
int const count_increase = old_node_ptr.external_count - 2;
89+
90+
// get argument node's internal 'count'
91+
node_counter old_counter = ptr->count.load(std::memory_order_relaxed);
92+
93+
// operating on node's count
94+
node_counter new_counter;
95+
do {
96+
new_counter = old_counter;
97+
--new_counter.external_counters; // 1
98+
new_counter.internal_count += count_increase; // 2
99+
} while (!ptr->count.compare_exchange_strong(old_counter, new_counter, // 3
100+
std::memory_order_acquire, std::memory_order_relaxed));
101+
102+
// delete node if all counters became 0
103+
if (!new_counter.internal_counter && !new_counter.external_counters) {
104+
delete ptr; // 4
105+
}
106+
};
41107
};
42108

43109
std::atomic<counted_node_ptr> head;
44110
std::atomic<counted_node_ptr> tail; // 1
45111

46112
public:
113+
/**
114+
* Pushes new node to the queue */
47115
void push(T new_value) {
48116

49117
std::unique_ptr<T> new_data(new T(new_value));
@@ -56,15 +124,44 @@ class lock_free_queue {
56124
increase_external_count(tail, old_tail); // 5
57125

58126
T* old_data = nullptr;
127+
// no other thread can modify tail while data is not nullptr
59128
if (old_tail.ptr->data.compare_exchange_strong(old_data, new_data.get())) // 6
60129
{
61130
old_tail.ptr->next = new_next;
62131
old_tail = tail.exchange(new_next);
132+
// now node is fully pushed (data is nullptr), so other threads
133+
// can proceed with their pushes
63134
free_external_counter(old_tail); // 7
64135
new_data.release();
65136
break;
66137
}
67-
old_tail.ptr->release_ref();
138+
old_tail.ptr->release_ref(); //
139+
}
140+
}
141+
142+
/**
143+
* Pops node from the queue */
144+
std::unique_ptr<T> pop() {
145+
counted_node_ptr old_head = head.load(std::memory_order_relaxed); // 1
146+
147+
for (;;) {
148+
increase_external_count(head, old_head); // 2
149+
node* const ptr = old_head.ptr;
150+
151+
// if queue is empty ('ptr' is null)
152+
if (ptr == tail.load().ptr) {
153+
prt->release_ref(); // 3
154+
return std::unique_ptr<T>();
155+
}
156+
157+
// this compares the external count and pointer as a single entity
158+
if (head.compare_exchange_strong(old_head, ptr->next)) { // 4
159+
T* const res = ptr->data.exchange(nullptr);
160+
free_external_count(old_head); // 5
161+
return std::unique_ptr<T>(res);
162+
}
163+
164+
ptr->release_ref; // 6
68165
}
69166
}
70167
};

0 commit comments

Comments
 (0)