├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── Visual Stdio ├── example.vcxproj ├── example.vcxproj.user ├── lfqueue.sln ├── lfqueue.vcxproj ├── lfqueue.vcxproj.user ├── test.vcxproj └── test.vcxproj.user ├── appveyor.yml ├── bin └── .gitkeep ├── cross-platform.h ├── example.c ├── lfq.c ├── lfq.h └── test_multithread.c /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio. 3 | ################################################################################ 4 | 5 | /Visual Stdio/x64 6 | /Visual Stdio/lfqueue.VC.db 7 | /Visual Stdio/lfqueue.VC.VC.opendb 8 | /Visual Stdio/.vs/lfqueue/v14 9 | /Visual Stdio/Release 10 | /Visual Stdio/Debug 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | compiler: gcc 3 | script: 4 | - make 5 | - make test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2017 Kautism 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS=-std=gnu99 -O3 -Wall -Wextra -g 3 | LDFLAGS=-g 4 | LOADLIBS=-lpthread 5 | 6 | all : bin/test_p1c1 bin/test_p4c4 bin/test_p100c10 bin/test_p10c100 bin/example 7 | 8 | bin/example: example.c liblfq.so.1.0.0 9 | gcc $(CFLAGS) $(LDFLAGS) example.c lfq.c -o bin/example -lpthread 10 | 11 | bin/test_p1c1: liblfq.so.1.0.0 test_multithread.c 12 | gcc $(CFLAGS) $(LDFLAGS) test_multithread.c -o bin/test_p1c1 -L. -Wl,-Bstatic -llfq -Wl,-Bdynamic -lpthread -D MAX_PRODUCER=1 -D MAX_CONSUMER=1 13 | 14 | bin/test_p4c4: liblfq.so.1.0.0 test_multithread.c 15 | gcc $(CFLAGS) $(LDFLAGS) test_multithread.c -o bin/test_p4c4 -L. -Wl,-Bstatic -llfq -Wl,-Bdynamic -lpthread -D MAX_PRODUCER=4 -D MAX_CONSUMER=4 16 | 17 | bin/test_p100c10: liblfq.so.1.0.0 test_multithread.c 18 | gcc $(CFLAGS) $(LDFLAGS) test_multithread.c -o bin/test_p100c10 -L. -Wl,-Bstatic -llfq -Wl,-Bdynamic -lpthread -D MAX_PRODUCER=100 -D MAX_CONSUMER=10 19 | 20 | bin/test_p10c100: liblfq.so.1.0.0 test_multithread.c 21 | gcc $(CFLAGS) $(LDFLAGS) test_multithread.c -o bin/test_p10c100 -L. -Wl,-Bstatic -llfq -Wl,-Bdynamic -lpthread -D MAX_PRODUCER=10 -D MAX_CONSUMER=100 22 | 23 | liblfq.so.1.0.0: lfq.c lfq.h cross-platform.h 24 | gcc $(CFLAGS) $(CPPFLAGS) -c lfq.c # -fno-pie for static linking? 25 | ar rcs liblfq.a lfq.o 26 | gcc $(CFLAGS) $(CPPFLAGS) -fPIC -c lfq.c 27 | gcc $(LDFLAGS) -shared -o liblfq.so.1.0.0 lfq.o 28 | 29 | test: bin/test_p1c1 bin/test_p4c4 bin/test_p100c10 bin/test_p10c100 30 | $(TESTWRAPPER) bin/test_p1c1 31 | $(TESTWRAPPER) bin/test_p4c4 32 | $(TESTWRAPPER) bin/test_p100c10 33 | $(TESTWRAPPER) bin/test_p10c100 34 | 35 | clean: 36 | rm -rf *.o bin/* liblfq.so.1.0.0 liblfq.a 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lfqueue [![Build Status](https://travis-ci.org/darkautism/lfqueue.svg?branch=HP)](https://travis-ci.org/darkautism/lfqueue) 2 | 3 | Minimize lock-free queue, it's easy to use and easy to read. It's only 150 line code so it is easy to understand, best code for education ever! 4 | 5 | ***Do not use these code in production*** 6 | 7 | Support multiple comsumer and multiple producer at sametime. 8 | 9 | | Arch | Build status | Test | 10 | | ----------------- | ------------ | ------------ | 11 | | Linux | [![Build Status](https://travis-ci.org/darkautism/lfqueue.svg?branch=HP)](https://travis-ci.org/darkautism/lfqueue)| On testing | 12 | | Windows(msbuild) | [![Build status](https://ci.appveyor.com/api/projects/status/yu04l5atf0j259kd?svg=true)](https://ci.appveyor.com/project/darkautism/lfqueue-2puqk) | Not tested [Issue](#free-memory-very-slow-in-visual-studio) | 13 | | Windows(Mingw) | [![Build status](https://ci.appveyor.com/api/projects/status/4nng8ye801ycyvgn/branch/HP?svg=true)](https://ci.appveyor.com/project/darkautism/lfqueue/branch/HP) | Not tested | 14 | | Windows(MinGW64) | [![Build status](https://ci.appveyor.com/api/projects/status/4457r35yh2x4f52d/branch/HP?svg=true)](https://ci.appveyor.com/project/darkautism/lfqueue-4jybw/branch/HP) | Not tested | 15 | | Windows(Cygwin) | [![Build status](https://ci.appveyor.com/api/projects/status/xb9oww8jtbaxa9so/branch/HP?svg=true)](https://ci.appveyor.com/project/darkautism/lfqueue-7hmwx/branch/HP) | Not tested | 16 | | Windows(Cygwin64) | [![Build status](https://ci.appveyor.com/api/projects/status/qjltyv4j963s86xd/branch/HP?svg=true)](https://ci.appveyor.com/project/darkautism/lfqueue-wepul/branch/HP) | Not tested | 17 | 18 | ## Build Guide 19 | 20 | In any gnu toolchain, just type `make`. 21 | 22 | In any visual studio build toolchain, just type `msbuild "Visual Stdio\lfqueue.sln" /verbosity:minimal /property:Configuration=Release /property:Platform=x86` or 64bit version `msbuild "Visual Stdio\lfqueue.sln" /verbosity:minimal /property:Configuration=Release /property:Platform=x64`. 23 | 24 | 25 | ## How to use 26 | 27 | Just copy past everywhere and use it. If you copy these code into your project. 28 | 29 | ## Next Milestone 30 | 31 | - Compile on MACOS ( **I do not have MAC, need somebody help!!** ) 32 | - Compile in kernel module. 33 | - Use lock-free memory manager. (free is very slow in windows) 34 | 35 | ## Example 36 | 37 | ### Sample example 38 | 39 | It is an minimize sample code for how to using lfq. 40 | 41 | **Even if int or long value is valid input data, but you will hard to distinguish empty queue or other error message.** 42 | 43 | **We not suggestion use int or long as queue data type** 44 | 45 | ``` c 46 | #include 47 | #include 48 | #include "lfq.h" 49 | 50 | int main() { 51 | long ret; 52 | struct lfq_ctx ctx; 53 | lfq_init(&ctx, 0); 54 | lfq_enqueue(&ctx,(void *)1); 55 | lfq_enqueue(&ctx,(void *)3); 56 | lfq_enqueue(&ctx,(void *)5); 57 | lfq_enqueue(&ctx,(void *)8); 58 | lfq_enqueue(&ctx,(void *)4); 59 | lfq_enqueue(&ctx,(void *)6); 60 | 61 | while ( (ret = (long)lfq_dequeue(&ctx)) != 0 ) 62 | printf("lfq_dequeue %ld\n", ret); 63 | 64 | lfq_clean(&ctx); 65 | return 0; 66 | } 67 | ``` 68 | 69 | ### Advance example 70 | 71 | If you want to get best performance with the cost of developing speed, you can control thread_id yourself. 72 | 73 | **This API only impilement on HP branch** 74 | 75 | ``` c 76 | #include 77 | #include 78 | #include "lfq.h" 79 | 80 | #define MAX_CONSUMER_THREAD 4 81 | 82 | int main() { 83 | long ret; 84 | struct lfq_ctx ctx; 85 | lfq_init(&ctx, MAX_CONSUMER_THREAD); 86 | lfq_enqueue(&ctx,(void *)1); 87 | 88 | // The second number is thread id, this thread id should unique between threads. 89 | // And this tid must less than MAX_CONSUMER_THREAD 90 | // In this sample code, this tid must 0, 1, 2, 3. 91 | ret = (long)lfq_dequeue_tid(&ctx, 1); 92 | 93 | lfq_clean(&ctx); 94 | return 0; 95 | } 96 | ``` 97 | 98 | ## API 99 | 100 | ### lfq_init(struct lfq_ctx *ctx, int max_consume_thread) 101 | 102 | Init lock-free queue. 103 | 104 | **Arguments:** 105 | - **ctx** : Lock-free queue handler. 106 | - **max_consume_thread** : Max consume thread numbers. If this value set to zero, use default value (16). 107 | 108 | **Return:** The lfq_init() functions return zero on success. On error, this functions return negative errno. 109 | 110 | 111 | ### lfq_clean(struct lfq_ctx *ctx) 112 | 113 | Clean lock-free queue from ctx. 114 | 115 | **Arguments:** 116 | - **ctx** : Lock-free queue handler. 117 | 118 | **Return:** The lfq_clean() functions return zero on success. On error, this functions return -1. 119 | 120 | 121 | ### lfq_enqueue(struct lfq_ctx *ctx, void * data) 122 | 123 | Push data into queue. 124 | 125 | **Arguments:** 126 | - **ctx** : Lock-free queue handler. 127 | - **data** : User data. 128 | 129 | **Return:** The lfq_clean() functions return zero on success. On error, this functions return negative errno. 130 | 131 | 132 | ### lfq_dequeue(struct lfq_ctx *ctx) 133 | 134 | Pop data from queue. 135 | 136 | **Arguments:** 137 | - **ctx** : Lock-free queue handler. 138 | 139 | **Return:** The lfq_clean() functions return zero if empty queue. Return positive pointer. On error, this functions return negative errno. 140 | 141 | ### lfq_dequeue_tid(struct lfq_ctx *ctx, int tid) 142 | 143 | Pop data from queue. 144 | 145 | **Arguments:** 146 | - **ctx** : Lock-free queue handler. 147 | - **tid** : Unique thread id. 148 | 149 | **Return:** The lfq_dequeue_tid() functions return zero if empty queue. Return positive pointer. On error, this functions return negative errno. 150 | 151 | ## Issues 152 | 153 | ### ENOMEM 154 | 155 | This lfqueue do not have size limit, so count your struct yourself. 156 | 157 | ### Can i iterate lfqueue inner struct? 158 | 159 | No, iterate inner struct is not threadsafe. 160 | 161 | If you do not get segmentation fault because you are iterate lfqueue in single thread. 162 | 163 | We should always iterate lfqueue by `lfq_enqueue` and `lfq_dequeue` method. 164 | 165 | ### CPU time slice waste? Other thread blocking and waiting for swap? 166 | 167 | **Enqueue** 168 | 169 | No, CAS operator not lock. Loser not block and wait winner. 170 | 171 | If a thread race win a CAS, other thread will get false and try to retrive next pointer. Because winner already swap tail, so other losers can do next race. 172 | 173 | **Example:** 174 | ``` 175 | 4 thread race push 176 | 1 win Push A, 2 lose, 1 do not have CPU time slice 177 | 1 win go out queue, 2 losers race A as tail, 1 race initnode as tail 178 | 1 win go out queue, 1 win push B, 1 lose, 1 race A failed because tail not initnode now 179 | ``` 180 | 181 | So lock-free queue have better performance then lock queue. 182 | 183 | **Dequeue** 184 | 185 | We choice Hazard Pointers to reaolve ABA problems. So dequeue is fast as enqueue. 186 | 187 | There has many papers to resolve this problem: 188 | 189 | - Lock-Free Reference Counting 190 | - ABA-Prevention Tags 191 | - [Hazard Pointers](https://github.com/darkautism/lfqueue/tree/HP) **Beta** 192 | - [Algorithm Paper](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.395.378&rep=rep1&type=pdf) 193 | - [Fucking stupid head wait](https://github.com/darkautism/lfqueue/tree/FSHW) **Stable** 194 | 195 | ### Free memory very slow in Visual studio 196 | 197 | Sorry, i have no idea why. Still finding problems in windows. 198 | 199 | ## Contributions 200 | 201 | [pcordes](https://github.com/pcordes) 202 | 203 | ## License 204 | 205 | WTFPL 206 | -------------------------------------------------------------------------------- /Visual Stdio/example.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {F97F305D-2A2A-4610-919C-C91CE4152C67} 23 | Win32Proj 24 | example 25 | 8.1 26 | 27 | 28 | 29 | Application 30 | true 31 | v140 32 | Unicode 33 | 34 | 35 | Application 36 | false 37 | v140 38 | true 39 | Unicode 40 | 41 | 42 | Application 43 | true 44 | v140 45 | Unicode 46 | 47 | 48 | Application 49 | false 50 | v140 51 | true 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | true 74 | 75 | 76 | true 77 | 78 | 79 | false 80 | 81 | 82 | false 83 | 84 | 85 | 86 | NotUsing 87 | Level3 88 | Disabled 89 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 90 | CompileAsC 91 | 92 | 93 | Console 94 | true 95 | 96 | 97 | 98 | 99 | NotUsing 100 | Level3 101 | Disabled 102 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 103 | CompileAsC 104 | 105 | 106 | Console 107 | true 108 | 109 | 110 | 111 | 112 | Level3 113 | NotUsing 114 | MaxSpeed 115 | true 116 | true 117 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 118 | CompileAsC 119 | 120 | 121 | Console 122 | true 123 | true 124 | true 125 | 126 | 127 | 128 | 129 | Level3 130 | NotUsing 131 | MaxSpeed 132 | true 133 | true 134 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 135 | CompileAsC 136 | 137 | 138 | Console 139 | true 140 | true 141 | true 142 | 143 | 144 | 145 | 146 | {1e11b533-4ccf-40e5-ad9d-5aaa19abc776} 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /Visual Stdio/example.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /Visual Stdio/lfqueue.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lfqueue", "lfqueue.vcxproj", "{1E11B533-4CCF-40E5-AD9D-5AAA19ABC776}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example", "example.vcxproj", "{F97F305D-2A2A-4610-919C-C91CE4152C67}" 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test.vcxproj", "{19641A1F-792B-4792-AD1C-005FE5EAD651}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|x64 = Debug|x64 15 | Debug|x86 = Debug|x86 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {1E11B533-4CCF-40E5-AD9D-5AAA19ABC776}.Debug|x64.ActiveCfg = Debug|x64 21 | {1E11B533-4CCF-40E5-AD9D-5AAA19ABC776}.Debug|x64.Build.0 = Debug|x64 22 | {1E11B533-4CCF-40E5-AD9D-5AAA19ABC776}.Debug|x86.ActiveCfg = Debug|Win32 23 | {1E11B533-4CCF-40E5-AD9D-5AAA19ABC776}.Debug|x86.Build.0 = Debug|Win32 24 | {1E11B533-4CCF-40E5-AD9D-5AAA19ABC776}.Release|x64.ActiveCfg = Release|x64 25 | {1E11B533-4CCF-40E5-AD9D-5AAA19ABC776}.Release|x64.Build.0 = Release|x64 26 | {1E11B533-4CCF-40E5-AD9D-5AAA19ABC776}.Release|x86.ActiveCfg = Release|Win32 27 | {1E11B533-4CCF-40E5-AD9D-5AAA19ABC776}.Release|x86.Build.0 = Release|Win32 28 | {F97F305D-2A2A-4610-919C-C91CE4152C67}.Debug|x64.ActiveCfg = Debug|x64 29 | {F97F305D-2A2A-4610-919C-C91CE4152C67}.Debug|x64.Build.0 = Debug|x64 30 | {F97F305D-2A2A-4610-919C-C91CE4152C67}.Debug|x86.ActiveCfg = Debug|Win32 31 | {F97F305D-2A2A-4610-919C-C91CE4152C67}.Debug|x86.Build.0 = Debug|Win32 32 | {F97F305D-2A2A-4610-919C-C91CE4152C67}.Release|x64.ActiveCfg = Release|x64 33 | {F97F305D-2A2A-4610-919C-C91CE4152C67}.Release|x64.Build.0 = Release|x64 34 | {F97F305D-2A2A-4610-919C-C91CE4152C67}.Release|x86.ActiveCfg = Release|Win32 35 | {F97F305D-2A2A-4610-919C-C91CE4152C67}.Release|x86.Build.0 = Release|Win32 36 | {19641A1F-792B-4792-AD1C-005FE5EAD651}.Debug|x64.ActiveCfg = Debug|x64 37 | {19641A1F-792B-4792-AD1C-005FE5EAD651}.Debug|x64.Build.0 = Debug|x64 38 | {19641A1F-792B-4792-AD1C-005FE5EAD651}.Debug|x86.ActiveCfg = Debug|Win32 39 | {19641A1F-792B-4792-AD1C-005FE5EAD651}.Debug|x86.Build.0 = Debug|Win32 40 | {19641A1F-792B-4792-AD1C-005FE5EAD651}.Release|x64.ActiveCfg = Release|x64 41 | {19641A1F-792B-4792-AD1C-005FE5EAD651}.Release|x64.Build.0 = Release|x64 42 | {19641A1F-792B-4792-AD1C-005FE5EAD651}.Release|x86.ActiveCfg = Release|Win32 43 | {19641A1F-792B-4792-AD1C-005FE5EAD651}.Release|x86.Build.0 = Release|Win32 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /Visual Stdio/lfqueue.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {1E11B533-4CCF-40E5-AD9D-5AAA19ABC776} 23 | Win32Proj 24 | lfqueue 25 | 8.1 26 | lfqueue 27 | 28 | 29 | 30 | StaticLibrary 31 | true 32 | v140 33 | Unicode 34 | 35 | 36 | StaticLibrary 37 | false 38 | v140 39 | true 40 | Unicode 41 | 42 | 43 | StaticLibrary 44 | true 45 | v140 46 | Unicode 47 | 48 | 49 | StaticLibrary 50 | false 51 | v140 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | true 78 | 79 | 80 | false 81 | 82 | 83 | false 84 | 85 | 86 | 87 | NotUsing 88 | Level3 89 | Disabled 90 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 91 | CompileAsC 92 | 93 | 94 | Console 95 | true 96 | 97 | 98 | 99 | 100 | NotUsing 101 | Level3 102 | Disabled 103 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 104 | CompileAsC 105 | 106 | 107 | Console 108 | true 109 | 110 | 111 | 112 | 113 | Level3 114 | NotUsing 115 | MaxSpeed 116 | true 117 | true 118 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 119 | CompileAsC 120 | 121 | 122 | Console 123 | true 124 | true 125 | true 126 | 127 | 128 | 129 | 130 | Level3 131 | NotUsing 132 | MaxSpeed 133 | true 134 | true 135 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 136 | CompileAsC 137 | 138 | 139 | Console 140 | true 141 | true 142 | true 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /Visual Stdio/lfqueue.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | _NO_DEBUG_HEAP=1 5 | WindowsLocalDebugger 6 | 7 | 8 | _NO_DEBUG_HEAP=1 9 | WindowsLocalDebugger 10 | 11 | -------------------------------------------------------------------------------- /Visual Stdio/test.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {19641A1F-792B-4792-AD1C-005FE5EAD651} 23 | Win32Proj 24 | test 25 | 8.1 26 | 27 | 28 | Application 29 | true 30 | v140 31 | Unicode 32 | 33 | 34 | Application 35 | false 36 | v140 37 | true 38 | Unicode 39 | 40 | 41 | Application 42 | true 43 | v140 44 | Unicode 45 | 46 | 47 | Application 48 | false 49 | v140 50 | true 51 | Unicode 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | true 74 | 75 | 76 | true 77 | 78 | 79 | false 80 | 81 | 82 | false 83 | 84 | 85 | 86 | NotUsing 87 | Level3 88 | Disabled 89 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 90 | CompileAsC 91 | 92 | 93 | Console 94 | true 95 | 96 | 97 | 98 | 99 | NotUsing 100 | Level3 101 | Disabled 102 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 103 | CompileAsC 104 | 105 | 106 | Console 107 | true 108 | 109 | 110 | 111 | 112 | Level3 113 | NotUsing 114 | MaxSpeed 115 | true 116 | true 117 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 118 | CompileAsC 119 | 120 | 121 | Console 122 | true 123 | true 124 | true 125 | 126 | 127 | 128 | 129 | Level3 130 | NotUsing 131 | MaxSpeed 132 | true 133 | true 134 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 135 | CompileAsC 136 | 137 | 138 | Console 139 | true 140 | true 141 | true 142 | 143 | 144 | 145 | 146 | {1e11b533-4ccf-40e5-ad9d-5aaa19abc776} 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /Visual Stdio/test.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | _NO_DEBUG_HEAP=1 5 | WindowsLocalDebugger 6 | 7 | 8 | _NO_DEBUG_HEAP=1 9 | WindowsLocalDebugger 10 | 11 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # version format 2 | version: 1.0.{build} 3 | 4 | branches: 5 | # whitelist 6 | only: 7 | - master 8 | - HP 9 | 10 | environment: 11 | matrix: 12 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 13 | PLATFORM: x86 14 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 15 | PLATFORM: x64 16 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 17 | PLATFORM: x86 18 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 19 | PLATFORM: x64 20 | 21 | build_script: 22 | - msbuild "Visual Stdio\lfqueue.sln" /verbosity:minimal /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" /property:Configuration=Release /property:Platform=%PLATFORM% 23 | -------------------------------------------------------------------------------- /bin/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkautism/lfqueue/944a77cb6a05d2a800beac736424b12171fc709c/bin/.gitkeep -------------------------------------------------------------------------------- /cross-platform.h: -------------------------------------------------------------------------------- 1 | #ifndef __CROSS_PLATFORM_H__ 2 | #define __CROSS_PLATFORM_H__ 3 | // bool define 4 | #ifdef __KERNEL__ 5 | #include 6 | #else 7 | #include 8 | #endif 9 | 10 | // malloc free 11 | #ifdef __KERNEL__ 12 | #define malloc(x) kmalloc(x, GFP_KERNEL ) 13 | #define free kfree 14 | #define calloc(x,y) kmalloc(x*y, GFP_KERNEL | __GFP_ZERO ) 15 | #include 16 | #else 17 | #include 18 | #include 19 | #endif 20 | 21 | 22 | #ifndef asm 23 | #define asm __asm 24 | #endif 25 | 26 | #define cmpxchg( ptr, _old, _new ) { \ 27 | volatile uint32_t *__ptr = (volatile uint32_t *)(ptr); \ 28 | uint32_t __ret; \ 29 | asm volatile( "lock; cmpxchgl %2,%1" \ 30 | : "=a" (__ret), "+m" (*__ptr) \ 31 | : "r" (_new), "0" (_old) \ 32 | : "memory"); \ 33 | ); \ 34 | __ret; \ 35 | } 36 | 37 | //#define CAS cmpxchg 38 | #define ATOMIC_SET __sync_lock_test_and_set 39 | #define ATOMIC_RELEASE __sync_lock_release 40 | 41 | #if defined __GNUC__ 42 | #define ATOMIC_SUB __sync_sub_and_fetch 43 | #define ATOMIC_SUB64 ATOMIC_SUB 44 | #define CAS __sync_bool_compare_and_swap 45 | #define XCHG __sync_lock_test_and_set // yes really. The 2nd arg is limited to 1 on machines with TAS but not XCHG. On x86 it's an arbitrary value 46 | #define ATOMIC_ADD __sync_add_and_fetch 47 | #define ATOMIC_ADD64 ATOMIC_ADD 48 | #define mb __sync_synchronize 49 | #if defined(__x86_64__) || defined(__i386) 50 | // #define lmb() asm volatile( "lfence" ) 51 | // #define smb() asm volatile( "sfence" ) 52 | #define lmb() asm volatile("":::"memory") // compiler barrier only. runtime reordering already impossible on x86 53 | #define smb() asm volatile("":::"memory") 54 | // "mfence" for lmb and smb makes assertion failures rarer, but doesn't eliminate, so it's just papering over the symptoms 55 | #endif // else no definition 56 | 57 | // thread 58 | #include 59 | #include 60 | #define THREAD_WAIT(x) pthread_join(x, NULL); 61 | #define THREAD_ID pthread_self 62 | #define THREAD_FN void * 63 | #define THREAD_YIELD sched_yield 64 | #define THREAD_TOKEN pthread_t 65 | 66 | #else 67 | #include 68 | #define ATOMIC_SUB(x,y) InterlockedExchangeAddNoFence(x, -y) 69 | #define ATOMIC_SUB64(x,y) InterlockedExchangeAddNoFence64(x, -y) 70 | #define ATOMIC_ADD InterlockedExchangeAddNoFence 71 | #define ATOMIC_ADD64 InterlockedExchangeAddNoFence64 72 | #ifdef _WIN64 73 | #define mb() MemoryBarrier() 74 | #define lmb() LoadFence() 75 | #define smb() StoreFence() 76 | inline bool __CAS(LONG64 volatile *x, LONG64 y, LONG64 z) { 77 | return InterlockedCompareExchangeNoFence64(x, z, y) == y; 78 | } 79 | #define CAS(x,y,z) __CAS((LONG64 volatile *)x, (LONG64)y, (LONG64)z) 80 | #else 81 | #define mb() asm mfence 82 | #define lmb() asm lfence 83 | #define smb() asm sfence 84 | inline bool __CAS(LONG volatile *x, LONG y, LONG z) { 85 | return InterlockedCompareExchangeNoFence(x, z, y) == y; 86 | } 87 | #define CAS(x,y,z) __CAS((LONG volatile *)x, (LONG)y, (LONG)z) 88 | #endif 89 | 90 | // thread 91 | #include 92 | #define THREAD_WAIT(x) WaitForSingleObject(x, INFINITE); 93 | #define THREAD_ID GetCurrentThreadId 94 | #define THREAD_FN WORD WINAPI 95 | #define THREAD_YIELD SwitchToThread 96 | #define THREAD_TOKEN HANDLE 97 | #endif 98 | #endif 99 | 100 | -------------------------------------------------------------------------------- /example.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "lfq.h" 4 | 5 | int main() { 6 | long ret; 7 | struct lfq_ctx ctx; 8 | lfq_init(&ctx, 1); 9 | lfq_enqueue(&ctx,(void *)1); 10 | lfq_enqueue(&ctx,(void *)3); 11 | lfq_enqueue(&ctx,(void *)5); 12 | lfq_enqueue(&ctx,(void *)8); 13 | lfq_enqueue(&ctx,(void *)4); 14 | lfq_enqueue(&ctx,(void *)6); 15 | 16 | while ( (ret = (long)lfq_dequeue(&ctx)) != 0 ) 17 | printf("lfq_dequeue %ld\n", ret); 18 | 19 | lfq_clean(&ctx); 20 | return 0; 21 | } -------------------------------------------------------------------------------- /lfq.c: -------------------------------------------------------------------------------- 1 | #include "cross-platform.h" 2 | #include "lfq.h" 3 | #ifdef DEBUG 4 | #include 5 | #endif 6 | #include 7 | #define MAXFREE 150 8 | 9 | static 10 | int inHP(struct lfq_ctx *ctx, struct lfq_node * lfn) { 11 | for ( int i = 0 ; i < ctx->MAXHPSIZE ; i++ ) { 12 | //lmb(); // not needed, we don't care if loads reorder here, just that we check all the elements 13 | if (ctx->HP[i] == lfn) 14 | return 1; 15 | } 16 | return 0; 17 | } 18 | 19 | static 20 | void enpool(struct lfq_ctx *ctx, struct lfq_node * lfn) { 21 | // add to tail of the free list 22 | lfn->free_next = NULL; 23 | volatile struct lfq_node *old_tail = XCHG(&ctx->fpt, lfn); // seq_cst 24 | old_tail->free_next = lfn; 25 | 26 | // getting nodes out of this will have exactly the same deallocation problem 27 | // as the main queue. 28 | // TODO: a stack might be easier to manage, but would increase contention. 29 | 30 | /* 31 | volatile struct lfq_node * p; 32 | do { 33 | p = ctx->fpt; 34 | } while(!CAS(&ctx->fpt, p, lfn)); // exchange using CAS 35 | p->free_next = lfn; 36 | */ 37 | } 38 | 39 | static 40 | void free_pool(struct lfq_ctx *ctx, bool freeall ) { 41 | if (!CAS(&ctx->is_freeing, 0, 1)) 42 | return; // this pool free is not support multithreading. 43 | volatile struct lfq_node * p; 44 | 45 | for ( int i = 0 ; i < MAXFREE || freeall ; i++ ) { 46 | p = ctx->fph; 47 | if ( (!p->can_free) || (!p->free_next) || inHP(ctx, (struct lfq_node *)p) ) 48 | goto exit; 49 | ctx->fph = p->free_next; 50 | free((void *)p); 51 | } 52 | exit: 53 | ctx->is_freeing = false; 54 | smb(); 55 | } 56 | 57 | static 58 | void safe_free(struct lfq_ctx *ctx, struct lfq_node * lfn) { 59 | if (lfn->can_free && !inHP(ctx,lfn)) { 60 | // free is not thread-safe 61 | if (CAS(&ctx->is_freeing, 0, 1)) { 62 | lfn->next = (void*)-1; // poison the pointer to detect use-after-free 63 | free(lfn); // we got the lock; actually free 64 | ctx->is_freeing = false; 65 | smb(); 66 | } else // we didn't get the lock; only add to a freelist 67 | enpool(ctx, lfn); 68 | } else 69 | enpool(ctx, lfn); 70 | free_pool(ctx, false); 71 | } 72 | 73 | static 74 | int alloc_tid(struct lfq_ctx *ctx) { 75 | for (int i = 0; i < ctx->MAXHPSIZE; i++) 76 | if (ctx->tid_map[i] == 0) 77 | if (CAS(&ctx->tid_map[i], 0, 1)) 78 | return i; 79 | 80 | return -1; 81 | } 82 | 83 | static 84 | void free_tid(struct lfq_ctx *ctx, int tid) { 85 | ctx->tid_map[tid]=0; 86 | } 87 | 88 | int lfq_init(struct lfq_ctx *ctx, int max_consume_thread) { 89 | struct lfq_node * tmpnode = calloc(1,sizeof(struct lfq_node)); 90 | if (!tmpnode) 91 | return -errno; 92 | 93 | struct lfq_node * free_pool_node = calloc(1,sizeof(struct lfq_node)); 94 | if (!free_pool_node) 95 | return -errno; 96 | 97 | tmpnode->can_free = free_pool_node->can_free = true; 98 | memset(ctx, 0, sizeof(struct lfq_ctx)); 99 | ctx->MAXHPSIZE = max_consume_thread; 100 | ctx->HP = calloc(max_consume_thread,sizeof(struct lfq_node)); 101 | ctx->tid_map = calloc(max_consume_thread,sizeof(struct lfq_node)); 102 | ctx->head = ctx->tail=tmpnode; 103 | ctx->fph = ctx->fpt=free_pool_node; 104 | 105 | return 0; 106 | } 107 | 108 | 109 | long lfg_count_freelist(const struct lfq_ctx *ctx) { 110 | long count=0; 111 | struct lfq_node *p = (struct lfq_node *)ctx->fph; // non-volatile 112 | while(p) { 113 | count++; 114 | p = p->free_next; 115 | } 116 | 117 | return count; 118 | } 119 | 120 | int lfq_clean(struct lfq_ctx *ctx){ 121 | if ( ctx->tail && ctx->head ) { // if have data in queue 122 | struct lfq_node *tmp; 123 | while ( (struct lfq_node *) ctx->head ) { // while still have node 124 | tmp = (struct lfq_node *) ctx->head->next; 125 | safe_free(ctx, (struct lfq_node *)ctx->head); 126 | ctx->head = tmp; 127 | } 128 | ctx->tail = 0; 129 | } 130 | if ( ctx->fph && ctx->fpt ) { 131 | free_pool(ctx, true); 132 | if ( ctx->fph != ctx->fpt ) 133 | return -1; 134 | free((void *)ctx->fpt); // free the empty node 135 | ctx->fph=ctx->fpt=0; 136 | } 137 | if ( !ctx->fph && !ctx->fpt ) { 138 | free((void *)ctx->HP); 139 | free((void *)ctx->tid_map); 140 | memset(ctx,0,sizeof(struct lfq_ctx)); 141 | } else 142 | return -1; 143 | 144 | return 0; 145 | } 146 | 147 | int lfq_enqueue(struct lfq_ctx *ctx, void * data) { 148 | struct lfq_node * insert_node = calloc(1,sizeof(struct lfq_node)); 149 | if (!insert_node) 150 | return -errno; 151 | insert_node->data=data; 152 | // mb(); // we've only written to "private" memory that other threads can't see. 153 | volatile struct lfq_node *old_tail; 154 | #if 0 155 | do { 156 | old_tail = (struct lfq_node *) ctx->tail; 157 | } while(!CAS(&ctx->tail,old_tail,insert_node)); 158 | #else 159 | old_tail = XCHG(&ctx->tail, insert_node); 160 | #endif 161 | // We've claimed our spot in the insertion order by modifying tail 162 | // we are the only inserting thread with a pointer to the old tail. 163 | 164 | // now we can make it part of the list by overwriting the NULL pointer in the old tail 165 | // This is safe whether or not other threads have updated ->next in our insert_node 166 | #ifdef DEBUG 167 | assert(!(old_tail->next) && "old tail wasn't NULL"); 168 | #endif 169 | old_tail->next = insert_node; 170 | // TODO: could a consumer thread could have freed the old tail? no because that would leave head=NULL 171 | 172 | // ATOMIC_ADD( &ctx->count, 1); 173 | return 0; 174 | } 175 | 176 | void * lfq_dequeue_tid(struct lfq_ctx *ctx, int tid ) { 177 | //int cn_runtimes = 0; 178 | volatile struct lfq_node *old_head, *new_head; 179 | #if 1 // HP[tid] stuff is necessary for deallocation. (but it's still not safe). 180 | do { 181 | retry: // continue jumps to the bottom of the loop, and would attempt a CAS with uninitialized new_head 182 | old_head = ctx->head; 183 | ctx->HP[tid] = old_head; // seq-cst store. (better: use xchg instead of mov + mfence on x86) 184 | mb(); 185 | 186 | if (old_head != ctx->head) // another thread freed it before seeing our HP[tid] store 187 | goto retry; 188 | new_head = old_head->next; // FIXME: crash with old_head=NULL during deallocation (tid=5)? (main thread=25486, this=25489) 189 | if (new_head==0 /* || new_head != old_head->next*/ ){ // redoing the same load isn't useful 190 | ctx->HP[tid] = 0; 191 | return 0; // never remove the last node 192 | } 193 | #ifdef DEBUG 194 | assert(new_head != (void*)-1 && "read an already-freed node"); 195 | #endif 196 | } while( ! CAS(&ctx->head, old_head, new_head) ); 197 | #else // without HP[] stuff 198 | do { 199 | old_head = ctx->head; 200 | //ctx->HP[tid] = old_head; 201 | new_head = old_head->next; 202 | //if (old_head != ctx->head) continue; 203 | if (!new_head) { 204 | // ctx->HP[tid] = 0; 205 | return 0; // never remove the last node 206 | } 207 | #ifdef DEBUG 208 | assert(new_head != (void*)-1 && "read an already-freed node"); 209 | #endif 210 | } while( !CAS(&ctx->head, old_head, new_head) ); 211 | #endif 212 | // mb(); // CAS is already a memory barrier, at least on x86. 213 | 214 | // we've atomically advanced head, and we're the thread that won the race to claim a node 215 | // We return the data from the *new* head. 216 | // The list starts off with a dummy node, so the current head is always a node that's already been read. 217 | 218 | ctx->HP[tid] = 0; 219 | void *ret = new_head->data; 220 | new_head->can_free = true; 221 | // ATOMIC_SUB( &ctx->count, 1 ); 222 | 223 | //old_head->next = (void*)-1; // done in safe-free in the actual free() path. poison the pointer to detect use-after-free 224 | 225 | // we need to avoid freeing until other readers are definitely not going to load its ->next in the CAS loop 226 | safe_free(ctx, (struct lfq_node *)old_head); 227 | 228 | //free(old_head); 229 | return ret; 230 | } 231 | 232 | void * lfq_dequeue(struct lfq_ctx *ctx ) { 233 | //return lfq_dequeue_tid(ctx, 0); // TODO: let this inline even in the shared library 234 | // old version 235 | int tid = alloc_tid(ctx); 236 | if (tid==-1) 237 | return (void *)-1; // To many thread race 238 | 239 | void * ret = lfq_dequeue_tid(ctx, tid); 240 | free_tid(ctx, tid); 241 | return ret; 242 | } 243 | -------------------------------------------------------------------------------- /lfq.h: -------------------------------------------------------------------------------- 1 | #ifndef __LFQ_H__ 2 | #define __LFQ_H__ 3 | #include "cross-platform.h" 4 | 5 | #include // C11 6 | 7 | struct lfq_node{ 8 | void * data; 9 | union { 10 | struct lfq_node * volatile next; 11 | struct lfq_node * volatile free_next; 12 | }; 13 | volatile int can_free; 14 | }; 15 | 16 | struct lfq_ctx{ 17 | alignas(64) volatile struct lfq_node * volatile head; 18 | int volatile count; 19 | volatile struct lfq_node * * HP; 20 | volatile int * tid_map; 21 | int volatile is_freeing; 22 | volatile struct lfq_node * volatile fph; // free pool head 23 | volatile struct lfq_node * volatile fpt; // free pool tail 24 | int MAXHPSIZE; 25 | 26 | alignas(64) volatile struct lfq_node * volatile tail; // in another cache line to avoid contention 27 | }; 28 | 29 | int lfq_init(struct lfq_ctx *ctx, int max_consume_thread); 30 | int lfq_clean(struct lfq_ctx *ctx); 31 | long lfg_count_freelist(const struct lfq_ctx *ctx); 32 | 33 | int lfq_enqueue(struct lfq_ctx *ctx, void * data); 34 | void * lfq_dequeue_tid(struct lfq_ctx *ctx, int tid ); 35 | void * lfq_dequeue(struct lfq_ctx *ctx ); 36 | 37 | /********************************************************** 38 | * 39 | * This macro will dequeue forever. 40 | * If you do not like high cost cpu, 41 | * use original dequeue function with memory barrier, 42 | * and sleep/thread_yield will be better idea. 43 | * 44 | *********************************************************/ 45 | #define LFQ_MB_DEQUEUE(ctx, ret) ({ \ 46 | do { \ 47 | ret = lfq_dequeue(ctx); \ 48 | mb(); \ 49 | } while(ret == 0); \ 50 | }) 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /test_multithread.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "lfq.h" 9 | #include "cross-platform.h" 10 | 11 | #if defined __GNUC__ || defined __CYGWIN__ || defined __MINGW32__ 12 | #include 13 | #include 14 | #else 15 | #include 16 | #endif 17 | 18 | 19 | #ifndef MAX_PRODUCER 20 | #define MAX_PRODUCER 100 21 | #endif 22 | #ifndef MAX_CONSUMER 23 | #define MAX_CONSUMER 10 24 | #endif 25 | 26 | 27 | #define SOMEID 667814649 28 | 29 | volatile uint64_t cn_added = 0; 30 | volatile uint64_t cn_deled = 0; 31 | 32 | volatile int cn_t = 0; 33 | volatile int cn_producer = 0; 34 | 35 | struct user_data{ 36 | long data; 37 | }; 38 | 39 | THREAD_FN addq( void * data ) { 40 | struct lfq_ctx * ctx = data; 41 | int ret = 0; 42 | long added; 43 | for (added = 0 ; added < 500000 ; added++) { 44 | struct user_data * p = malloc(sizeof(struct user_data)); 45 | p->data=SOMEID; 46 | if ( ( ret = lfq_enqueue(ctx,p) ) != 0 ) { 47 | printf("lfq_enqueue failed, reason:%s\n", strerror(-ret)); 48 | ATOMIC_ADD64(&cn_added, added); 49 | ATOMIC_SUB(&cn_producer, 1); 50 | return 0; 51 | } 52 | } 53 | ATOMIC_ADD64(&cn_added, added); 54 | ATOMIC_SUB(&cn_producer, 1); 55 | printf("Producer thread [%lu] exited! Still %d running...\n",THREAD_ID(), cn_producer); 56 | return 0; 57 | } 58 | 59 | THREAD_FN delq(void * data) { 60 | struct lfq_ctx * ctx = data; 61 | struct user_data * p; 62 | int tid = ATOMIC_ADD(&cn_t, 1); 63 | 64 | long deleted = 0; 65 | while(1) { 66 | p = lfq_dequeue_tid(ctx, tid); 67 | if (p) { 68 | if (p->data!=SOMEID){ 69 | printf("data wrong!!\n"); 70 | exit(1); 71 | } 72 | 73 | free(p); 74 | deleted++; 75 | } else { 76 | if (ctx->count || cn_producer) 77 | THREAD_YIELD(); // queue is empty, release CPU slice 78 | else 79 | break; // queue is empty and no more producers 80 | } 81 | } 82 | 83 | ATOMIC_ADD64(&cn_deled, deleted); 84 | 85 | // p = lfq_dequeue_tid(ctx, tid); 86 | printf("Consumer thread [%lu] exited %d\n",THREAD_ID(),cn_producer); 87 | return 0; 88 | } 89 | 90 | int main() { 91 | struct lfq_ctx ctx; 92 | int i = 0; 93 | lfq_init(&ctx, MAX_CONSUMER); 94 | THREAD_TOKEN thread_d[MAX_CONSUMER]; 95 | THREAD_TOKEN thread_a[MAX_PRODUCER]; 96 | 97 | ATOMIC_ADD(&cn_producer, 1); 98 | for ( i = 0 ; i < MAX_CONSUMER ; i++ ) { 99 | #if defined __GNUC__ || defined __CYGWIN__ || defined __MINGW32__ 100 | pthread_create(&thread_d[i], NULL, delq, (void*)&ctx); 101 | #else 102 | #pragma warning(disable:4133) 103 | thread_d[i] = CreateThread(NULL, 0, delq, &ctx, 0, 0); 104 | #endif 105 | } 106 | 107 | for ( i = 0 ; i < MAX_PRODUCER ; i++ ){ 108 | ATOMIC_ADD(&cn_producer, 1); 109 | #if defined __GNUC__ || defined __CYGWIN__ || defined __MINGW32__ 110 | pthread_create(&thread_a[i], NULL, addq, (void*)&ctx); 111 | #else 112 | #pragma warning(disable:4133) 113 | thread_a[i] = CreateThread(NULL, 0, addq, &ctx, 0, 0); 114 | #endif 115 | 116 | } 117 | 118 | ATOMIC_SUB(&cn_producer, 1); 119 | 120 | for (i = 0; i < MAX_PRODUCER; i++) 121 | THREAD_WAIT(thread_a[i]); 122 | 123 | for ( i = 0 ; i < MAX_CONSUMER ; i++ ) 124 | THREAD_WAIT(thread_d[i]); 125 | 126 | long freecount = lfg_count_freelist(&ctx); 127 | int clean = lfq_clean(&ctx); 128 | 129 | printf("Total push %"PRId64" elements, pop %"PRId64" elements. freelist=%ld, clean = %d\n", cn_added, cn_deled, freecount, clean); 130 | if ( cn_added == cn_deled ) 131 | printf("Test PASS!!\n"); 132 | else 133 | printf("Test Failed!!\n"); 134 | 135 | return (cn_added != cn_deled); 136 | } 137 | --------------------------------------------------------------------------------