├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── data ├── 64-200.png ├── lmeans-64-200 └── means-64-200 ├── mpsc.c ├── mpsc_test.c └── mpscq.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.a 2 | *.o 3 | *.so 4 | gmon.out 5 | mpsc_test 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Use this code however you may see fit, as long as you maintain the 2 | comments at the top the source code. 3 | 4 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 5 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 6 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 7 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 8 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 9 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 10 | OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2015 Daniel Bittman : http://dbittman.github.io/ 2 | 3 | CFLAGS=-Wall -Wextra -Werror -std=gnu11 -O3 4 | LDFLAGS= 5 | LDLIBS=-lpthread 6 | CC=gcc 7 | 8 | ifeq ($(strip $(DEBUG)),tsan) 9 | CC=clang 10 | CFLAGS+=-fsanitize=thread 11 | LDFLAGS+=-fsanitize=thread 12 | else ifeq ($(strip $(DEBUG)),asan) 13 | CC=clang 14 | CFLAGS+=-fsanitize=address 15 | LDFLAGS+=-fsanitize=address 16 | endif 17 | 18 | all: libmpscq.so libmpscq.a mpsc_test 19 | 20 | mpsc_test: mpsc.o mpsc_test.o 21 | 22 | mpsc_test.o: mpsc_test.c 23 | 24 | mpsc.o: mpsc.c mpscq.h 25 | 26 | libmpscq.a: mpsc.o 27 | ar -cvq libmpscq.a mpsc.o 28 | 29 | libmpscq.so: mpsc.c mpscq.h Makefile 30 | $(CC) $(CFLAGS) -shared -o libmpscq.so -fPIC mpsc.c 31 | 32 | clean: 33 | -rm libmpscq.* *.o mpsc_test 34 | 35 | prof: mpsc_test 36 | for i in $$(seq 1 100); do sudo nice -n -20 ./mpsc_test; mv -f gmon.out gmon.out.$$i; done 37 | gprof -s ./mpsc_test gmon.out.*; gprof ./mpsc_test gmon.sum -bQ 38 | 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MPSCQ - Multiple Producer, Single Consumer Wait-Free Queue 2 | ========================================================== 3 | C11 library that allows multiple threads to enqueue something to a queue, and allows one thread (and only one thread) to dequeue from it. 4 | 5 | This code is tested, but not proven. Use it at your own peril. 6 | 7 | Interface 8 | --------- 9 | Creation and destruction of a queue can be done with: 10 | 11 | struct mpscq *mpscq_create(struct mpscq *n, size_t capacity); 12 | void mpscq_destroy(struct mpscq *q); 13 | 14 | Passing a NULL pointer as _n_ will allocate a new queue with malloc, initialize it, and return it. Passing a pointer to a struct mpscq as _n_ will initialize that object. Calling the destroy function will free the internal data of the object, and if the object was allocated via malloc, it will be freed as well. 15 | 16 | Enqueuing can be done with: 17 | 18 | bool mpscq_enqueue(struct mpscq *q, void *obj); 19 | 20 | which will enqueue _obj_ in _q_, returning true if it was enqueued and false if it wasn't (queue was full). 21 | 22 | Dequeuing can be done with: 23 | 24 | void *mpscq_dequeue(struct mpscq *q); 25 | 26 | which will return NULL if the queue was empty or an object from the queue if it wasn't. Note that 27 | a queue may appear to be empty if a thread is in the process if writing the object in the next slot in the buffer, but that's okay because the function can be called again (see the comments in the source for more interesting comments on this). 28 | 29 | The queue may also be queried for current number of items and for total capacity: 30 | 31 | size_t mpscq_capacity(struct mpscq *q); 32 | size_t mpscq_count(struct mpscq *q); 33 | 34 | Comments 35 | -------- 36 | PLEASE report bugs to me if you find any (email me at danielbittman1@gmail.com). 37 | 38 | Technical Details 39 | ----------------- 40 | During the first half of the enqueuing function, we prevent writing to the queue if the queue is full. This is done by doing an add anyway, and then seeing if the old value was greater than or equal to max. If so, then we cannot write to the queue because it's full. This is safe for multiple threads, since the worst thing that can happen is a thread sees the count to be way above the max. This is okay, since it'll just report the queue as being full. 41 | 42 | The second half of the enqueuing function gains near-exclusive access to the head element. It isn't completely exclusive, since the consumer thread may be observing that element. However, we prevent any producer threads from trying to write to the same area of the queue. Once head is fetched and incremented, we store the object to the head location, thus releasing that memory location. 43 | 44 | In the dequeue function, we exchange the tail with NULL, and observe the return value. If the return value is NULL, then there's nothing in the queue and so we return NULL. If we got back an object, we just increment the tail and decrement the count, before returning. 45 | 46 | Performance (preliminary) 47 | ------------------------- 48 | Here's a quick comparison to a locked circular queue I wrote quickly, fueled by beer. With 64 threads, each writing 200 objects to the queue with the speed of 64 fairly slow threads (and, of course, a singular thread reading from it with the speed of a one fairly slow thread... ) the lock-free queue wins pretty convincingly: 49 | 50 | ![I WILL WRITE 500 OBJECTS, AND I WILL WRITE 500 MORE](https://raw.githubusercontent.com/dbittman/waitfree-mpsc-queue/master/data/64-200.png) 51 | 52 | (hard to see: the left-most data points are at x=50, not 0) 53 | 54 | Well, that's pretty nice. If your queue is small, then MPSCQ does wonders compared to locking, which is what I would expect. 55 | 56 | -------------------------------------------------------------------------------- /data/64-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dbittman/waitfree-mpsc-queue/020ba2262c48b24828bed98e5cc63c5529ca24ce/data/64-200.png -------------------------------------------------------------------------------- /data/lmeans-64-200: -------------------------------------------------------------------------------- 1 | 50 860.97 2 | 450 505.85 3 | 850 386.29 4 | 1250 358.71 5 | 1650 251.43 6 | 2050 256.77 7 | 2450 229.72 8 | 2850 235.15 9 | 3250 168.17 10 | 3650 152.26 11 | 4050 145.67 12 | 4450 150.03 13 | 4850 167.51 14 | 5250 144.98 15 | 5650 157.39 16 | 6050 121.42 17 | 6450 131.01 18 | 6850 105.88 19 | 7250 119.45 20 | 7650 108.03 21 | 8050 143.55 22 | 8450 111.33 23 | 8850 107.54 24 | 9250 92.02 25 | 9650 112.6 26 | 10050 102.3 27 | 10450 94.12 28 | 10850 102.36 29 | 11250 102.26 30 | 11650 94.43 31 | 12050 87.08 32 | 12450 89.49 33 | 12850 85.4 34 | 13250 84.51 35 | 13650 85.61 36 | 14050 82.17 37 | 14450 83.93 38 | 14850 89.81 39 | 15250 79.43 40 | 15650 82 41 | 16050 81.19 42 | 16450 79.62 43 | 16850 79.62 44 | 17250 80.31 45 | 17650 79.04 46 | 18050 81.74 47 | 18450 78.78 48 | 18850 80.6 49 | 19250 79.81 50 | 19650 79.13 51 | 20050 79.27 52 | 20450 79.71 53 | 20850 81.15 54 | 21250 79.12 55 | 21650 79.26 56 | 22050 79.12 57 | 22450 79.35 58 | 22850 79.14 59 | 23250 79.39 60 | 23650 80.1 61 | 24050 78.49 62 | 24450 79.86 63 | 24850 79.19 64 | 25250 78.23 65 | 25650 80.26 66 | 26050 79.83 67 | 26450 79.05 68 | 26850 79.62 69 | 27250 79.17 70 | 27650 80.14 71 | 28050 80.54 72 | 28450 78.57 73 | 28850 80.49 74 | 29250 78.96 75 | 29650 79.78 76 | 30050 79.49 77 | 30450 79.15 78 | 30850 78.71 79 | 31250 79.66 80 | 31650 79.31 81 | 32050 79.51 82 | 32450 77.89 83 | 32850 79.2 84 | 33250 79.11 85 | 33650 78.81 86 | 34050 78.41 87 | 34450 78.82 88 | 34850 77.99 89 | 35250 79.3 90 | 35650 80.08 91 | 36050 78.52 92 | 36450 79.96 93 | 36850 80.1 94 | 37250 79.45 95 | 37650 79.62 96 | 38050 79.06 97 | 38450 78.94 98 | 38850 80.75 99 | 39250 80.14 100 | 39650 79.65 101 | 40050 79.36 102 | 40450 78.67 103 | 40850 79.95 104 | 41250 79.58 105 | 41650 78.29 106 | 42050 78.63 107 | 42450 78.81 108 | 42850 79.54 109 | 43250 79.27 110 | 43650 78.73 111 | 44050 79.3 112 | 44450 78.72 113 | 44850 80.26 114 | 45250 80.61 115 | 45650 79.96 116 | 46050 78.74 117 | 46450 80.24 118 | 46850 79.55 119 | 47250 79.39 120 | 47650 78.94 121 | 48050 79.02 122 | 48450 79.64 123 | 48850 78.58 124 | 49250 80.15 125 | 49650 79.59 126 | -------------------------------------------------------------------------------- /data/means-64-200: -------------------------------------------------------------------------------- 1 | 50 95.14 2 | 450 68.97 3 | 850 62.81 4 | 1250 67.54 5 | 1650 81.86 6 | 2050 67.5 7 | 2450 74.89 8 | 2850 59.35 9 | 3250 62.08 10 | 3650 58.8 11 | 4050 54.12 12 | 4450 62.22 13 | 4850 78.34 14 | 5250 59.76 15 | 5650 53.9 16 | 6050 53.22 17 | 6450 53.98 18 | 6850 54.87 19 | 7250 46.89 20 | 7650 47.05 21 | 8050 46.98 22 | 8450 48.91 23 | 8850 52.5 24 | 9250 45.67 25 | 9650 45.52 26 | 10050 45.73 27 | 10450 45.29 28 | 10850 45.66 29 | 11250 46.21 30 | 11650 45.25 31 | 12050 45.85 32 | 12450 45.72 33 | 12850 45.83 34 | 13250 46.06 35 | 13650 45.34 36 | 14050 46.05 37 | 14450 46 38 | 14850 45.71 39 | 15250 45.78 40 | 15650 46.61 41 | 16050 44.48 42 | 16450 45.1 43 | 16850 45.62 44 | 17250 45.77 45 | 17650 45.33 46 | 18050 45.65 47 | 18450 45.99 48 | 18850 45.85 49 | 19250 46.57 50 | 19650 45.72 51 | 20050 45.74 52 | 20450 45.73 53 | 20850 46.36 54 | 21250 45.98 55 | 21650 45.88 56 | 22050 46.53 57 | 22450 46.26 58 | 22850 46.05 59 | 23250 46.39 60 | 23650 45.74 61 | 24050 45.47 62 | 24450 45.93 63 | 24850 45.95 64 | 25250 45.75 65 | 25650 45.56 66 | 26050 45.69 67 | 26450 45.73 68 | 26850 46.67 69 | 27250 46.95 70 | 27650 46.1 71 | 28050 46.06 72 | 28450 45.88 73 | 28850 45.32 74 | 29250 45.87 75 | 29650 46.06 76 | 30050 44.71 77 | 30450 45.49 78 | 30850 45.23 79 | 31250 47.05 80 | 31650 46.2 81 | 32050 45.5 82 | 32450 45.57 83 | 32850 46.3 84 | 33250 44.79 85 | 33650 45.04 86 | 34050 46.67 87 | 34450 45.81 88 | 34850 45.35 89 | 35250 46.06 90 | 35650 46.46 91 | 36050 45.99 92 | 36450 45.61 93 | 36850 45.95 94 | 37250 45.84 95 | 37650 46.12 96 | 38050 46.11 97 | 38450 46.08 98 | 38850 45.88 99 | 39250 44.78 100 | 39650 45.99 101 | 40050 46.44 102 | 40450 45.98 103 | 40850 46.1 104 | 41250 46.14 105 | 41650 46.14 106 | 42050 46.16 107 | 42450 45.82 108 | 42850 45.23 109 | 43250 45.85 110 | 43650 45.7 111 | 44050 46.55 112 | 44450 46.03 113 | 44850 46.39 114 | 45250 46.05 115 | 45650 46.29 116 | 46050 46.49 117 | 46450 46.17 118 | 46850 46.17 119 | 47250 46.84 120 | 47650 45.65 121 | 48050 45.87 122 | 48450 46.26 123 | 48850 45.84 124 | 49250 45.32 125 | 49650 45.26 126 | -------------------------------------------------------------------------------- /mpsc.c: -------------------------------------------------------------------------------- 1 | /* 2015 Daniel Bittman : http://dbittman.github.io/ */ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "mpscq.h" 9 | 10 | /* multi-producer, single consumer queue * 11 | * Requirements: max must be >= 2 */ 12 | struct mpscq *mpscq_create(struct mpscq *n, size_t capacity) 13 | { 14 | if(!n) { 15 | n = calloc(1, sizeof(*n)); 16 | n->flags |= MPSCQ_MALLOC; 17 | } else { 18 | n->flags = 0; 19 | } 20 | n->count = ATOMIC_VAR_INIT(0); 21 | n->head = ATOMIC_VAR_INIT(0); 22 | n->tail = 0; 23 | n->buffer = calloc(capacity, sizeof(void *)); 24 | n->max = capacity; 25 | atomic_thread_fence(memory_order_release); 26 | return n; 27 | } 28 | 29 | void mpscq_destroy(struct mpscq *q) 30 | { 31 | free(q->buffer); 32 | if(q->flags & MPSCQ_MALLOC) 33 | free(q); 34 | } 35 | 36 | bool mpscq_enqueue(struct mpscq *q, void *obj) 37 | { 38 | size_t count = atomic_fetch_add_explicit(&q->count, 1, memory_order_acquire); 39 | if(count >= q->max) { 40 | /* back off, queue is full */ 41 | atomic_fetch_sub_explicit(&q->count, 1, memory_order_release); 42 | return false; 43 | } 44 | 45 | /* increment the head, which gives us 'exclusive' access to that element */ 46 | size_t head = atomic_fetch_add_explicit(&q->head, 1, memory_order_acquire); 47 | assert(q->buffer[head % q->max] == 0); 48 | void *rv = atomic_exchange_explicit(&q->buffer[head % q->max], obj, memory_order_release); 49 | assert(rv == NULL); 50 | return true; 51 | } 52 | 53 | void *mpscq_dequeue(struct mpscq *q) 54 | { 55 | void *ret = atomic_exchange_explicit(&q->buffer[q->tail], NULL, memory_order_acquire); 56 | if(!ret) { 57 | /* a thread is adding to the queue, but hasn't done the atomic_exchange yet 58 | * to actually put the item in. Act as if nothing is in the queue. 59 | * Worst case, other producers write content to tail + 1..n and finish, but 60 | * the producer that writes to tail doesn't do it in time, and we get here. 61 | * But that's okay, because once it DOES finish, we can get at all the data 62 | * that has been filled in. */ 63 | return NULL; 64 | } 65 | if(++q->tail >= q->max) 66 | q->tail = 0; 67 | size_t r = atomic_fetch_sub_explicit(&q->count, 1, memory_order_release); 68 | assert(r > 0); 69 | return ret; 70 | } 71 | 72 | size_t mpscq_count(struct mpscq *q) 73 | { 74 | return atomic_load_explicit(&q->count, memory_order_relaxed); 75 | } 76 | 77 | size_t mpscq_capacity(struct mpscq *q) 78 | { 79 | return q->max; 80 | } 81 | 82 | -------------------------------------------------------------------------------- /mpsc_test.c: -------------------------------------------------------------------------------- 1 | /* 2015 Daniel Bittman : http://dbittman.github.io/ */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "mpscq.h" 9 | 10 | struct mpscq *queue; 11 | _Atomic int amount_produced = ATOMIC_VAR_INIT(0); 12 | _Atomic int amount_consumed = ATOMIC_VAR_INIT(0); 13 | _Atomic bool done = ATOMIC_VAR_INIT(false); 14 | _Atomic int retries = ATOMIC_VAR_INIT(0); 15 | _Atomic long long total = ATOMIC_VAR_INIT(0); 16 | #define NUM_ITEMS 10000 17 | #define NUM_THREADS 32 18 | 19 | struct item { 20 | _Atomic int sent, recv; 21 | }; 22 | 23 | struct item items[NUM_THREADS][NUM_ITEMS]; 24 | 25 | void *producer_main(void *x) 26 | { 27 | long tid = (long)x; 28 | struct timespec start, end; 29 | for(int i=0;isent, 1) == 1); 60 | assert(atomic_fetch_add(&it->recv, 1) == 0); 61 | doublechecked = false; 62 | } else if(done && doublechecked) { 63 | break; 64 | } else if(done) { 65 | doublechecked = true; 66 | } 67 | } 68 | assert(!mpscq_dequeue(queue)); 69 | atomic_thread_fence(memory_order_seq_cst); 70 | assert(queue->count == 0); 71 | assert(queue->head % queue->max == queue->tail); 72 | pthread_exit(0); 73 | } 74 | 75 | #include 76 | int main(int argc, char **argv) 77 | { 78 | (void)argc; 79 | (void)argv; 80 | int num_producers = NUM_THREADS-1; 81 | pthread_t producers[num_producers]; 82 | pthread_t consumer; 83 | 84 | struct timespec start, end; 85 | 86 | for(int i=0;i: http://dbittman.github.io/ */ 2 | #ifndef __MPSCQ_H 3 | #define __MPSCQ_H 4 | 5 | #include 6 | #ifndef __cplusplus 7 | #include 8 | #endif 9 | #include 10 | #include 11 | 12 | #define MPSCQ_MALLOC 1 13 | 14 | #ifndef __cplusplus 15 | struct mpscq { 16 | _Atomic size_t count; 17 | _Atomic size_t head; 18 | size_t tail; 19 | size_t max; 20 | void * _Atomic *buffer; 21 | int flags; 22 | }; 23 | #else 24 | struct mpscq; 25 | #endif 26 | 27 | #ifdef __cplusplus 28 | extern "C" { 29 | #endif 30 | /* create a new mpscq. If n == NULL, it will allocate 31 | * a new one and return it. If n != NULL, it will 32 | * initialize the structure that was passed in. 33 | * capacity must be greater than 1, and it is recommended 34 | * to be much, much larger than that. It must also be a power of 2. */ 35 | struct mpscq *mpscq_create(struct mpscq *n, size_t capacity); 36 | 37 | /* enqueue an item into the queue. Returns true on success 38 | * and false on failure (queue full). This is safe to call 39 | * from multiple threads */ 40 | bool mpscq_enqueue(struct mpscq *q, void *obj); 41 | 42 | /* dequeue an item from the queue and return it. 43 | * THIS IS NOT SAFE TO CALL FROM MULTIPLE THREADS. 44 | * Returns NULL on failure, and the item it dequeued 45 | * on success */ 46 | void *mpscq_dequeue(struct mpscq *q); 47 | 48 | /* get the number of items in the queue currently */ 49 | size_t mpscq_count(struct mpscq *q); 50 | 51 | /* get the capacity of the queue */ 52 | size_t mpscq_capacity(struct mpscq *q); 53 | 54 | /* destroy a mpscq. Frees the internal buffer, and 55 | * frees q if it was created by passing NULL to mpscq_create */ 56 | void mpscq_destroy(struct mpscq *q); 57 | 58 | #ifdef __cplusplus 59 | } 60 | #endif 61 | 62 | #endif 63 | 64 | --------------------------------------------------------------------------------