@@ -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
46112public:
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