#ifndef LOCK_FREE_QUEUE_H #define LOCK_FREE_QUEUE_H /** * How and why this lock free queue works: * * Adapted from the paper titled "Simple, Fast, and Practical Non-Blocking * and Blocking Concurrent Queue Algorithms" by Maged M. Michael, * Michael L. Scott. * * Safety Properties: * * 1. The linked list is always connected. * 2. Nodes are only inserted after the last node in the linked list. * 3. Nodes are only deleted from the beginning of the linked list. * 4. Head always points to the first node in the linked list. * 5. Tail always points to a node in the linked list. * * * 1. The linked list is always connected because the next pointer is not set * to null before the node is freed, and no node is freed until deleted * from the linked list. * * 2. Nodes are only inserted at the end of the linked list because they are * linked through the tail pointer which always points to a node in the * linked list (5) and an inserted node is only linked to a node that has * a null next pointer, and the only such node is the last one (1). * * 3. Nodes are deleted from the beginning of the list because they are * deleted only when they are pointed to by head which always points to the * first node (4). * * 4. Head always points to the first node in the list because it only changes * its value to the next node atomically. The new value of head cannot be * null because if there is only one node in the list the dequeue operation * returns without deleting any nodes. * * 5. Tail always points to a node in the linked list because it never lags * behind head, so it can never point to a deleted node. Also, when tail * changes its value it always swings to the next node in the list and it * never tires to change its value if the next pointer is NULL. */ #include template class lock_free_queue { struct node_t; struct pointer_t { node_t *node; uint32_t count; pointer_t() : node(NULL), count(0) { // Nop. } pointer_t(node_t *node, uint32_t count) { this->node = node; this->count = count; } bool equals(pointer_t &other) { return node == other.node && count == other.count; } }; struct node_t { T value; pointer_t next; node_t() { next.node = NULL; next.count = 0; } node_t(pointer_t next, T value) { this->next = next; this->value = value; } }; // Always points to the first node in the list. pointer_t head; // Always points to a node in the list, (not necessarily the last). pointer_t tail; // Compare and swap counted pointers, we can only do this if pointr_t is // 8 bytes or less since that the maximum size CAS can handle. bool compare_and_swap(pointer_t *address, pointer_t *oldValue, pointer_t newValue) { if (sync::compare_and_swap( (uint64_t*) address, *(uint64_t*) oldValue, *(uint64_t*) &newValue)) { return true; } return false; } public: lock_free_queue() { // We can only handle 64bit CAS for counted pointers, so this will // not work with 64bit pointers. assert (sizeof(pointer_t) == sizeof(uint64_t)); // Allocate a dummy node to be used as the first node in the list. node_t *node = new node_t(); // Head and tail both start out pointing to the dummy node. head.node = node; tail.node = node; } virtual ~lock_free_queue() { // Delete dummy node. delete head.node; } bool is_empty() { return head.node == tail.node; } virtual void enqueue(T value) { // Create a new node to be inserted in the linked list, and set the // next node to NULL. node_t *node = new node_t(); node->value = value; node->next.node = NULL; pointer_t tail; // Keep trying until enqueue is done. while (true) { // Read the current tail which may either point to the last node // or to the second to last node (not sure why second to last, // and not any other node). tail = this->tail; // Reads the next node after the tail which will be the last node // if null. pointer_t next; if (tail.node != NULL) { next = tail.node->next; } // Loop if another thread changed the tail since we last read it. if (tail.equals(this->tail) == false) { continue; } // If next is not pointing to the last node try to swing tail to // the last node and loop. if (next.node != NULL) { compare_and_swap(&this->tail, &tail, pointer_t(next.node, tail.count + 1)); continue; } // Try to link node at the end of the linked list. if (compare_and_swap(&tail.node->next, &next, pointer_t(node, next.count + 1))) { // Enqueueing is done. break; } } // Enqueue is done, try to swing tail to the inserted node. compare_and_swap(&this->tail, &tail, pointer_t(node, tail.count + 1)); } bool dequeue(T *value) { pointer_t head; // Keep trying until dequeue is done. while(true) { head = this->head; pointer_t tail = this->tail; pointer_t next = head.node->next; if (head.equals(this->head) == false) { continue; } // If queue is empty, or if tail is falling behind. if (head.node == tail.node) { // If queue is empty. if (next.node == NULL) { return false; } // Tail is falling behind, advance it. compare_and_swap(&this->tail, &tail, pointer_t(next.node, tail.count + 1)); } else { // Read value before CAS, otherwise another // dequeue might advance it. *value = next.node->value; if (compare_and_swap(&this->head, &head, pointer_t(next.node, head.count + 1))) { break; } } } delete head.node; return true; } }; #endif /* LOCK_FREE_QUEUE_H */