Rev 0.5
1
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
This document is a part of the Effo Core project, and is copyrighted by Pat Huang and/or others who
actually wrote it.
Redistribution and use in 'plain source' form (i.e. ASCII text) and 'compiled' forms (HTML, PDF, RTF
and so forth) with or without modification, are permitted provided that the following conditions are
met:
1. Redistributions of 'plain source' form must retain the above copyright notice, this list of
conditions and the following disclaimer as the first lines of this file unmodified.
THIS DOCUMENTATION IS PROVIDED BY THE EFFO CORE PROJECT "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE EFFO CORE PROJECT BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS DOCUMENTATION, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
2
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
Table of Content
Lock-free Queue and Ring Buffer..................................................................................1
1. Document Revision History........................................................................................ 5
2. Documents and Code URL.........................................................................................5
3. What will we Design..................................................................................................5
4. The Effo Project..........................................................................................................6
4.1 Introductions...................................................................................................... 6
4.2 Divide and Rule.................................................................................................7
5. Base LIFO...................................................................................................................7
5.1 Single-linked List...............................................................................................8
5.2 Creator............................................................................................................... 8
5.3 Node..................................................................................................................9
5.4 Head and Push-Front.......................................................................................11
5.5 Front and Pop-Front.........................................................................................13
5.6 Coding Example............................................................................................... 13
5.7 To-do Next.......................................................................................................13
6. Atomic Operations...................................................................................................13
6.1 i++ is not Atomic............................................................................................13
6.2 Get Atomic Primitive........................................................................................14
6.3 Compare with pthread Lock.............................................................................15
6.4 Test Results...................................................................................................... 16
6.5 sync_plc_none and efao Polices......................................................................17
7. sync_queue.............................................................................................................. 20
7.1 Tail, List Head and Queue Head.......................................................................20
7.2 front() and push()............................................................................................21
7.3 pop()................................................................................................................ 22
7.4 Coding Example............................................................................................... 23
8. Base Ringed............................................................................................................. 24
8.1 The Template...................................................................................................24
8.2 The Stuff..........................................................................................................25
8.3 The Snapshot................................................................................................... 26
8.4 The Operations................................................................................................27
8.5 How a Ringed Buffer Works.............................................................................27
8.6 SizeUsed and SizeFree.....................................................................................28
8.7 CanPush() and CanPop()..................................................................................29
8.8 Quick Accessing Host from a Guest.................................................................29
9. Ring Buffer............................................................................................................... 30
9.1 push()..............................................................................................................30
9.2 pop()................................................................................................................ 32
9.3 The Wrapper ring_buffer..................................................................................33
9.4 ring_buffer Code Example...............................................................................34
10. Ring Queue............................................................................................................34
10.1 push()............................................................................................................34
3
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
10.2 pop()..............................................................................................................35
10.3 The Wrapper ring_queue...............................................................................36
11. Policy-Based Dynamic-size and Fixed-size.............................................................37
11.1 IRingQ<DYNA>..............................................................................................37
11.2 IRingQ<STAT>...............................................................................................38
11.3 IRingBuf<STAT>............................................................................................38
11.4 IRingBuf<DYNA>...........................................................................................39
12. Support, Helps and Suggestions............................................................................39
4
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
Please note if you downloaded this document from the Effo Project site(s)
http://code.google.com/p/effo or
http://sourceforge.net/projects/effo
then next time please go to the Effo Core project site to download newer revision(s) of this
document, because the Effo Core project is different from the Effo Project.
The related documents and code could be downloaded from the Effo Core project site
http://effocore.googlecode.com or
http://code.google.com/p/effocore
To be sure always visit above site to get the latest code and documents of the Effo Core project.
Please visit the project for more other references.
Notes:
Lock-free: no lock/mutex needed but atomic operation is required
Doesn’t support lock-free: no atomic operations used in the type implementation
Total-free: neither lock/mutex nor atomic operation used so all operations are not atomically
5
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
See reference document Effo Core References of the Effo Core for Effo header files naming
convention such as *_inl.h or *_i.h etc. The Effo Core References could be download from the Effo
Core project site, see section Document and Code URL.
4.1 Introductions
The Effo Project is an Open Source project targets at C/C++ software system architectural design
and practical library development based on the design.
In fact Effo has other goals as below, and the Effo Framework, Effo Core and Effo Add-ons may
cover some items of them.
1. Effo Kits and Effo Libs/Components, or Effo Add-ons,
Add-ons, providing lots of C/C++ helpers,
libraries and kits to developers for quick applications and modules developing. For
example, multi-threading and network messaging.
2. Effo Framework,
Framework, the development platform on which C/C++ developers only need to
modify or add little code when developing their applications and modules; known as
Effortless Development or Effo Code Samples Driven Development.
Development.
3. Effo Utilities,
Utilities, Makefile auto-generating tool, Code auto-generating tools, release
generating and clean tools, memory test utility, SSO Burst tool, C9B.M. utility and other
useful tools.
4. Effo Kernels,
Kernels, some special-purpose kernels, i.e. Linux kernel variants w/o virtual memory
management, a kernel-only system (means no shell/user space), or kernel supports C++.
5. Effo Tiny, Effo Portable and Effo Compact,
Compact, some special-purpose OS and Linux
distributions, i.e. small Linux, portable and bootable multi-OS (2, 3 or 5) on 500M or 1G or
2G USB flash.
6. Effo Design, Effo Manual and Effo References,
References, not only help user understanding and using
Effo, but also provide generic C/C++ concepts, technique, design and development
references.
7. Effo Research, such as Highly-concurrent Programming, Code Samples Driven
Development, Parallel Computing and Distributed Computing.
8. Others…
Lots of works need to be done. More things to do, concerns of copyright and license become more
too.
6
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
As you know that one important character of Open Source is reuse. When you reuse something,
troubles come too in some cases. For example, if reuse copyleft source (i.e., GPLed) in the
development of a software, then you’d make the source of the whole software open. This
sometimes prevents developers from reusing excellent free software and Open Source software.
At the beginning of the Effo Project, we made a mistake that there’re 2 project sites; the main site is
on Google Code, http://effo.googlecode.com, and a backup is on SourceForge,
http://effo.sourceforge.net.
So the project need to maintain two SVNs which controls the same source code, and this causes
many problems. You can image that.
Because of the concerns of copyright and license, and the above problem, the Effo Project was then
divided into some projects, that are, Effo Member Projects. The original Effo sites (Effo@GoogleCode
and Effo@SourceForge) were made as the portals of the Effo Project now.
Especially the http://effo.sourceforge.net became the Effo Home page.
Now, Effo@GoogleCode and Effo@SourceForge will not update Effo source code anymore. All source
code was relocated to the Effo member projects listed as below:
1. Effo Core,
Core, http://effocore.googlecode.com, the Core of Effo, MIT license
2. Effo Addon,
Addon, http://effoaddon.googlecode.com, Add-ons for Effo, MIT license
3. Effo NetMsg,
NetMsg, http://effonetmsg.googlecode.com, Network & Messaging, MIT license
4. Effo Devel,
Devel, http://effodevel.googlecode.com, the Effo Framework, or Effo Development
Platform, MIT license
5. Effo GPLed,
GPLed, http://effogpled.googlecode.com, GPL licensed add-ons and applications like
memory test, drivers etc.
5. Base LIFO
You may use fixed-size array based queue, or linked list based queue. Here discuss linked list based
first.
7
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
Download the design document Effo Design - XLnk, a Generic Linked List in C from the Effo Core
project (see section Document and Code URL of the document) for more Linked List discussion.
There’re many kinds of lists, for example, single-linked, doubly linked, circular-linked, and so on.
Single-linked, 3 nodes
1 2 3
Head
Pointer
Single-linked, 3 nodes
Head 1 2 3
Pointer
Head 3 2 1
Pointer
5.2 Creator
If write C++, you’ll take advantages of type-strict, namespace and template to design a generic
LIFO:
template
<
class t,
template<class> class node_creator = creator_new
>
class lifo_list
See codebase/addons/inl/include/cont_inl.h of source tree of the Effo Core.
The template template creator should support a create() and a Destroy(), see
codebase/builtin/include/impl/effo_creator.h of source tree of the Effo Core for the examples:
template<class t>
struct creator_new {
static t *create(void)
{
return new t;
}
static void Destroy(t *p)
8
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
{
delete p;
}
};
This creator uses new() and delete(), and creator_new will be the default creator. By Effo
convention, name of a destroy function will always be Destroy(), not destroy(). Of couse if you like
the later, just make a change on above code.
Optionally, we may use another creator
template<class t>
struct creator_nofail {
static t *create(void)
{
t *ret;
while (NULL == (ret = (t*)malloc
(t*)malloc(sizeof
(sizeof *ret))) {
effo_msleep(1);
}
ret = new(ret) t;
return ret;
}
static void Destroy(t *p)
{
delete p;
}
};
In above code, you’ll note 2 things, malloc() call and new() placement.
It uses malloc() when allocating memory for an object instead of new().
If you try to new() an object or new()[] an array when the load of a system is very heavy and there’s
not enough memory, your program might abort, especially when it was built up as a debug version;
but malloc() won’t.
So use malloc() you’ll get an opportunity to correct something at runtime. Besides malloc(), the
create() won’t return until it get the memory requested.
That’s why I give a name creator_nofail.
new() placement will construct your malloc()ed object.
5.3 Node
9
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
10
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
…
You should revise your code accordingly.
If the List declared the node type using typedef like
class List
{
public:
struct node { node *next, *prev; };
public:
typedef node node_type;
…
then you could write the code as following
List::node_type *pNode = pInput->next;
…
and it will be always correct even if in List the “struct node” was renamed to “struct list_node”.
In List only the typedef line needs to be revised this way
struct list_node { list_node *next, *prev; };
public:
typedef list_node node_type;
In another place one should use typedef to reduce key-striking efforts
namespace Mapping {
map<unsigned long, pair<unsigned short, unsigned char*> > m_oMaps;
…
map<unsigned long, pair<unsigned short, unsigned char*> >::iterator it =
m_oMaps.find(MY_KEY);
pair<unsigned short, unsigned char*> oVal = it->second;
…
you’d better revise it as below
namespace Mapping {
typedef unsigned long key;
typedef pair<unsigned short, unsigned char*> value;
typedef map<key, value > mapping;
mapping m_oMaps;
…
mapping::iterator it = m_oMaps.find(MY_KEY);
value oVal = it->second;
…
We have already node_pointer data type for this list, now give the Head
…
protected:
node_pointer m_head;
The Head is the only data member of the list.
11
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
For a LIFO, we always insert a node just after the Head to let the new node be the first node of the
list, say the Push-Front operation.
…
public:
efinline iterator push_front(t &p)
{
node_pointer ret = node_add(m_head, p, true);
if (NULL != ret) {
m_head = ret;
}
return iterator(ret);
}
The efinline is not interesting at all however in some case it is useful.
When EFINLINE was specified to the compiler command by -DEFINLINE, efinline is exactly inline
keyword, otherwise it is nothing.
The result of using Effo EFINLINE option or not is shown as the examples below
Example 1. EFINLINE was defined using -DEFINLINE.
[pat@localhost devel] ll rls/all_applet_s/bin/
-rwxrwxr-x 1 pat pat 677060 2009-06-18 05:04 rls/all_applet_s/bin/effomem86
-rwxrwxr-x 1 pat pat 7833447 2009-06-18 05:09 rls/all_applet_s/bin/effomem86D
-rwxrwxr-x 1 pat pat 677060 2009-06-18 05:04 rls/all_applet_s/bin/getcpuinfo
-rwxrwxr-x 1 pat pat 7833447 2009-06-18 05:09 rls/all_applet_s/bin/getcpuinfoD
-rwxrwxr-x 1 pat pat 677060 2009-06-18 05:04 rls/all_applet_s/bin/getmeminfo
-rwxrwxr-x 1 pat pat 7833447 2009-06-18 05:09 rls/all_applet_s/bin/getmeminfoD
-rwxrwxr-x 1 pat pat 677060 2009-06-18 05:04 rls/all_applet_s/bin/netlooptest
-rwxrwxr-x 1 pat pat 7833447 2009-06-18 05:09 rls/all_applet_s/bin/netlooptestD
...
Example 2. EFINLINE was not defined.
[pat@localhost devel] ll rls/all_applet_s/bin/
-rwxrwxr-x 1 pat pat 468228 2009-06-18 05:12 rls/all_applet_s/bin/effomem86
-rwxrwxr-x 1 pat pat 6562021 2009-06-18 05:13 rls/all_applet_s/bin/effomem86D
-rwxrwxr-x 1 pat pat 468228 2009-06-18 05:12 rls/all_applet_s/bin/getcpuinfo
-rwxrwxr-x 1 pat pat 6562021 2009-06-18 05:13 rls/all_applet_s/bin/getcpuinfoD
-rwxrwxr-x 1 pat pat 468228 2009-06-18 05:12 rls/all_applet_s/bin/getmeminfo
-rwxrwxr-x 1 pat pat 6562021 2009-06-18 05:13 rls/all_applet_s/bin/getmeminfoD
-rwxrwxr-x 1 pat pat 468228 2009-06-18 05:12 rls/all_applet_s/bin/netlooptest
-rwxrwxr-x 1 pat pat 6562021 2009-06-18 05:13 rls/all_applet_s/bin/netlooptestD
...
The examples should make sense that if less inline code, final binary or executables would be
smaller because the compiler would not embed inlined code into caller but make it be a function
instead.
Please note the list will use iterator which is the same as std::list::iterator, so you know how to use
it.
12
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
As you had noted that the LIFO is very simple and it is not lock-free.
We introduced it because later of this document we will design Lock-free FIFO and it will be based
on this simple LIFO. So next we’ll
1. Introduce Atomic Operations, in next section
2. Design a Lock-free FIFO, in next of next
6. Atomic Operations
13
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
CAUTION:
Please keep in mind that none of these lines is guaranteed to be atomic even if on a single CPU
(and one core only) system
pBackup = pStr; // Type of pointer pBackup and pStr is char* pointer
--pDistance; // Type of pointer pDistance is long* pointer
i++; // Type of integer i is int
count = n + 10; // Type of integer count and n is int
…
14
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
If you have an old version of compiler, you’d make these atomic primitive yourself, just like what I
had done.
For example, if your GNU/C version is newer than 4.2.x, then you might use __sync_xxx series built-
in calls.
While note that old version of C++ compilers/libs doesn’t provide std::atomic, so do like what I had
done.
15
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
real 0m0.203s
user 0m0.166s
sys 0m0.005s
[pat@localhost atomic_test]$ time ./test lock
Test type: Lock
Count 0x3000000
real 0m2.351s
user 0m2.291s
sys 0m0.011s
[pat@localhost atomic_test]$ time ./test atomic
Test type: Atomic
Count 0x3000000
real 0m0.512s
user 0m0.468s
sys 0m0.008s
The time Linux utility was used to measure the execution time, in a Linux use “man time” to get the
details about this utility.
I had run the test on my Linux machine which only has one processor (uni-core), OS FC8/x86 plus
its default kernel.
See FC8/x86 default configuration as below
2.6.23.1-42.fc8
config-2.6.23.1-42.fc8
#
# Automatically generated make config: don't edit
# Linux kernel version: 2.6.23.1-42.fc8
# Tue Oct 30 13:53:02 2007
#
CONFIG_X86_32=y
16
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
...
CONFIG_SEMAPHORE_SLEEPERS=y
...
CONFIG_X86=y
...
CONFIG_M686=y
...
CONFIG_X86_GENERIC=y
...
CONFIG_X86_CMPXCHG=y
...
CONFIG_X86_XADD=y
...
CONFIG_RWSEM_XCHGADD_ALGORITHM=y
...
CONFIG_SCHED_SMT=y
CONFIG_SCHED_MC=y
# CONFIG_PREEMPT_NONE is not set
CONFIG_PREEMPT_VOLUNTARY=y
# CONFIG_PREEMPT is not set
CONFIG_PREEMPT_BKL=y
...
Maybe you have a data that you hope to operate it in some cases atomically, but in some case not.
For example if the data would be operated serially as designed, atomic operations is not necessary
at all. Anyway atomic operating wastes a little of you time although it is very faster than mutexed
operating.
Effo sync_plc_none (See codebase/builtin/include/impl/sync_plc_none.h of the Effo Core) and efao
(See codebase/addons/inl/include/atomic_inl.h of the Effo Core) are for these situations.
template<class t>
struct sync_plc_none {
sync_plc_none(void) { clear(0); }
sync_plc_none(t &p)
17
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
{
m_t = p;
}
inline void clear(t &p)
{
m_t = p;
}
inline void init(t &p)
{
m_t = p;
}
inline t &load(void)
{
return m_t;
}
/* for CAS of atomic_inl.h. */
inline void CAS(t val_new)
{
m_t = val_new;
}
/* for FetchAndAdd of atomic_inl.h. */
inline t FetchAndAdd(t val_add)
{
m_t += val_add;
return m_t;
}
protected:
t m_t;
};
All member functions are inlined so no performance loss.
Below is efao, Effo Atomic Operations
template<class t>
struct efao {
efao(void) { clear(0); }
efao(t &p)
{
m_t = p;
}
inline void clear(t p)
{
m_t = p;
}
inline void init(t p)
18
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
{
m_t = p;
}
inline t load(void)
{
return m_t;
}
/* Compare-and-Swap */
inline void CAS(t val_old, t val_new)
{
while (!compare_and_swap(&m_t, val_old, val_new));
}
inline void CAS(t val_new)
{
while (!compare_and_swap(&m_t, m_t, val_new));
}
/*
* Prevent from endless CAS loop.
* Set retry_count = -1 for retrying for ever.
*/
inline bool CAS(t val_old, t val_new, long retry_count)
{
while (!compare_and_swap(&m_t, val_old, val_new)) {
if (retry_count != -1 && 0 == --retry_count) {
return false;
}
}
return true;
}
inline t FetchAndAdd(t val_add)
{
return fetch_and_add(&m_t, val_add);
}
protected:
volatile t m_t;
};
Be sure both sync_plc_none and efao has these members
inline void CAS(t val_new)
inline t FetchAndAdd(t val_add)
because I designed sync_plc_none and efao using Policy-Based Design Pattern so we can specify
which one will be used for our queue, list or buffer in which one or more data could be chosen being
operated atomically or not.
19
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
7. sync_queue
Head 0 0 Q 3 4 T
Pointer
Q-Head Last Tail
Q-node Node
In above figure, the Head Pointer is the list Head of lifo_list and it is a pointer only.
The Queue is a segment or sub-list of the whole List. In above figure nodes from “Q” to “T”
compose the Queu, and all nodes from “Head Pointer” to “T” compose the whole List.
20
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
…
protected:
node_type m_tail;
plc_type m_sync_qhead;
};
A sync_queue has a Tail Node (m_tail in above code) which performs
The Tail (node “T”) of the List and the Queue
NEXT addresses last node (node “4”) of the List and the Queue
…
inline node_pointer last(void) { return m_tail.next; }
…
The only data which might to be operated atomically of a sync_queue is the Q-head (node “Q”), so
the Q-head type should be sync_policy.
Furthermore, the Tail was not dynamically allocated, so in sync_queue namespace-scoped the Tail is
always accessible and its address will not change.
inline iterator end(void) { return iterator(&m_tail); }
Note iterator of the List is not usable and not necessary out of a sync_queue. end() is an internal
call.
When constructing, an empty Q-head need to be pushed into the queue
sync_queue(void)
{
// Adding an empty Q-head node
t o = 0;
base_type::m_head = base_type::node_create(&m_tail, o);
m_sync_qhead.init(base_type::m_head);
m_tail.next = m_sync_qhead.load();
}
After above initializating an empty sync_queue looks like
An Empty sync_queue
Head Q T
Pointer
Q-Head Tail
Node
The first node of the Queue is not the first node of the List, but the node which next to the Q-head
inline t front(void)
21
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
{
iterator it(m_sync_qhead.load()->next);
return *it;
}
If push a data, a new node will be added to the end of the Queue
00 efinline bool push(t &p)
01 {
02 node_pointer n = this->push_back(p);
03 bool ret = n != &m_tail;
04 /* pop until no 0ed node(s) */
05 while (base_type::m_head != m_sync_qhead.load()) {
06 this->pop_front();
07 }
08 return ret;
09 }
Note in above code push_back() and pop_front() are operations of the List, the Queue is based the
List, so it uses these operations.
Before line 05, the sync_queue may looks like
Head 0 0 Q 3 4 5 T
Pointer
Q-Head Last Tail
Q-node Node
In above figure, node “5” is the new one. Nodes 3-5 are our Queue nodes now.
Code lines 05-07 do a clean to erase all aged nodes. After clean the sync_queue will be
After clean
Head Q 3 4 5 T
Pointer
Q-Head Last Tail
Q-node Node
7.3 pop()
22
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
Because among the List Head, Q-head and Tail, Q-head is the only one which needed to be
operated atomically. Whenever updating the Q-head, atomic operating is a must if require Lock-
free.
Afte pop() the sync_queue is
After pop()
The first node of the List is previous Q-head of the Queue, and previous node “3” is the Q-head
now.
Queue nodes are 4-5 because node “3” was popped.
23
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
}
}
}
Thread-B loop also could be
ThreadLoop_B()
{
while (m_bRunning) {
if (!oQue.is_empty()) {
serial_number val = oQue.front();
oQue.pop();
// do something using val here
}
}
}
Don’t forget to do a final clean after done
oQue.clear();
8. Base Ringed
24
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
…
From the previous sections of this document, we got known sync_policy, buf_creator already.
Template parameter is_q indicates if the ringed for Ring Buffer or Ring Queue.
In ringed namespace-scoped, we defines 3 things, the Stuff, Snapshot and Operations.
Related to the Stuff, Snapshot and Operations, we named the Ringed class a Host. So the Stuff,
Snapshot and Operations are all Guests of the Host.
A Stuff is reponsible for storing Tail, SizeUsed and SizeFree etc; it is also used for computing
something like new Head or new Tail of a ringed buffer for the Host.
…
template<class U, bool isq>
/*
* U for stopping complaint of
* g++ "explicit specificialization in non-namespace scope ..."
*/
struct stuff {
// Queue type, not buffer.
typedef ringed<t, sync_policy, buf_creator, is_q> host_type;
typedef typename host_type::size_type size_type;
typedef typename host_type::cell_type cell_type;
host_type *m_h;
/* A copy of host::m_head. */
cell_type m_pop;
/* A copy of host::m_tail. */
cell_type m_push;
cell_type m_new;
size_type m_used;
size_type m_free;
char m_sz_in;
…
Note “U” parameter is just used to avoid errors or warning of a compiler, because the Stuff is
defined in the ringed namespace-scoped.
Above default Stuff is for Ring Queeu.
If a Stuff is for Ring Buffer, it will be slightly different from above one (member functions are
different too)
…
template<class U>
struct stuff<U, false>
false> {
// Buffer type, not queue.
typedef ringed<t, sync_policy, buf_creator, is_q> host_type;
typedef typename host_type::size_type size_type;
25
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
host_type *m_h;
/* A copy of host::m_head. */
cell_type m_pop;
/* A copy of host::m_tail. */
cell_type m_push;
cell_type m_new;
size_type m_used;
size_type m_free;
size_type m_left;
size_type m_sz_left;
size_type m_sz_in;
…
A Snapshot is responsible for taking a snapshot of Tail and doing push()/pop() for the Host.
…
template<class U, bool isq>
/*
* U for stopping complaint of
* g++ "explicit specificialization in non-namespace scope ..."
*/
class snapshot
{
// Queue type, not buffer.
protected:
typedef stuff<U, is_q> stuff_type;
public:
typedef typename stuff_type::host_type host_type;
protected:
typedef typename host_type::size_type size_type;
typedef typename host_type::off_type off_type;
typedef typename host_type::data_type data_type;
typedef typename host_type::cell_type cell_type;
protected:
host_type *m_h;
stuff_type m_t;
…
This default Snapshot is for Ring Queue.
If a Snapshot is for Ring Buffer, it will be slightly different from above one (member functions are
different too); actually the Stuff it holds is different.
…
26
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
template<class U>
class snapshot<U, false>
{
// Buffer type, not queue.
protected:
typedef stuff<U, is_q> stuff_type;
…
The Operations is responsible for selecting Ring Buffer or Ring Queue and doing corresponding
push() and pop().
template<class U, bool isq>
/*
* U for stopping complaint of
* g++ "explicit specificialization in non-namespace scope ..."
*/
class operations
{
// Queue type, not buffer.
protected:
typedef snapshot<U, is_q> shot_type;
typedef typename shot_type::host_type host_type;
typedef typename host_type::data_type data_type;
typedef typename host_type::cell_type cell_type;
protected:
host_type *m_h;
This default Operations holds a Ring Queue type Snapshot.
If an Operations is for Ring Buffer, it will be slightly different from above one (member functions are
different too).
The held Snapshot is different.
27
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
Pop/PopFront
Discard/DiscardFront (optional)
If we push 4 bytes into it, the bytes will be put at the Tail of this buffer, now the buffer looks like:
Offset 01234567
Buffer [1 1 1 1 0 0 0 0]
Said the corresponding bytes (3:0) would be set to 1 to indicate in use (SizeUsed = 4), and there
are still 4 bytes (7:4) available (SizeFree = 4).
The Head is still at offset 0, but the Tail was set at offset 4 now (Head = 0, Tail = 4).
If you push bytes again, the first new byte would be put at the Tail (offset 4).
So we got
SizeUsed = Tail - Head = 4 - 0 = 4
SizeFree = BufferSize - SizeUsed = 8 - 4 = 4
If we pop 2 bytes from above buffer to make the buffer looks like:
Offset 01234567
Buffer [0 0 1 1 0 0 0 0]
Said the corresponding bytes (1:0) would be reset to 0 to indicate available again
SizeUsed = 2, SizeFree = 6
The head was set at offset 2 now, but buffer Tail is still at offset 4 (Head = 2, Tail = 4).
If you pop bytes again, the first output byte would be from the Head (offset 2).
So we got
SizeUsed = Tail - Head = 4 - 2 = 2
SizeFree = BufferSize - SizeUsed = 8 - 2 = 6
If you want to pop 3 or more bytes from above buffer, it will fail.
What's about push?
We always need to reserve 1 byte for identifying Tail from Head to make the logic simple and clear.
So if you want to push 6 or more bytes, it will fail.
28
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
CanPush and CanPop are internal members used when doing push() or pop().
static inline bool can_push(stuff<t, is_q> *p)
{
return (size_type)p->m_free > (size_type)p->m_sz_in;
}
An empty cell of the ringed buffer is always reserved, so if SizeFree >= InputLen + 1, then there’re
available cells into which we could push something.
If used cells are more than requested, then the cells could be popped
static inline bool can_pop(stuff<t, is_q> *p)
{
return (size_type)p->m_used >= (size_type)p->m_sz_in;
}
Following methods are for Guests such as Stuff, Snapshot and Operations internally.
protected:
inline cell_type buf(void)
{
return m_buf;
}
inline cell_type end(void)
{
return m_end;
}
inline const size_type cnt(void)
{
29
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
return m_cnt;
}
inline cell_type tail(void)
{
return m_sync_tail.load();
}
inline cell_type head(void)
{
return m_sync_head.load();
}
inline plc_type *sync_tail(void)
{
return &m_sync_tail;
}
inline plc_type *sync_head(void)
{
return &m_sync_head;
}
protected:
ops_type m_ops;
cell_type m_buf;
plc_type m_sync_head;
plc_type m_sync_tail;
cell_type m_end;
size_type m_cnt;
};
In above code we also get known that the Head and Tail of a Ring Buffer or Ring Queue should be
operated atomically if Lock-free required
9. Ring Buffer
9.1 push()
30
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
As mentioned ringed::operations is responsible for push() or pop() for a Ring Buffer or Ring Queue.
Below is for Ring Buffer in the ringed namespace-scoped, ringed::operations
template<class U>
class operations<U, false>
{
// Buffer type, not queue.
protected:
typedef snapshot<U, is_q> shot_type;
typedef typename shot_type::host_type host_type;
typedef typename host_type::size_type size_type;
typedef typename host_type::cell_type cell_type;
protected:
host_type *m_h;
public:
...
inline bool push(const cell_type p, const size_type count)
{
shot_type shot;
shot.host_set(m_h);
shot.take_input(count);
return shot.push(p);
}
...
A Snapshot pushes the data, ringed::snapshot::push():
...
inline bool push(const cell_type p)
{
m_t.compute(m_t.m_push);
if (m_h->can_push(&m_t)) {
if (m_t.broken
(m_t.broken())
()) {
effo_memcpy(m_t.m_push,
effo_memcpy(m_t.m_push, p, m_t.m_left);
effo_memcpy(m_h->buf(), p + m_t.m_left, m_t.m_sz_left);
} else {
effo_memcpy(m_t.m_push, p, m_t.m_sz_in);
}
m_h->sync_tail()->CAS
m_h->sync_tail()->CAS(m_t.m_new);
(m_t.m_new);
return true;
}
return false;
}
...
In above code broken() means before push(), the buffer looks like
Offset 01234567
31
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
Buffer [0 0 0 0 1 1 1 0]
That is, the input cells (i.e. 3 cells, cell-1, cell-2 and cell 3) need to be split into 2 parts, part-1 (i.e.
cell-1) will be put at the end of the buffer, and another part (part-2, cell-2 and cell-3) will be put at
the begging of the buffer.
So after push() the new buffer will be
Offset 01234567
Buffer [1
[1 1 0 0 1 1 1 1]
The new data at Offset 7, 0 and 1 were just pushed into the buffer.
After moved the data in, the Tail need to be updated atomically if Lock-free required.
See the reference document Effo Core References of the Effo Core and
codebase/addons/inl/include/bytes_inl.h of source tree of the Effo Core for more details about
effo_memcpy().
9.2 pop()
The ringed::operations::pop()
...
inline cell_type pop(cell_type p, const size_type count)
{
shot_type shot;
shot.host_set(m_h);
shot.take_input(count);
return shot.pop(p);
}
...
A Snapshot pops the data, ringed::snapshot::pop():
inline cell_type pop(cell_type p)
{
cell_type ret = NULL;
m_t.compute(m_t.m_pop);
if (m_h->can_pop(&m_t)) {
if (m_t.broken
(m_t.broken())
()) {
effo_memcpy(p, m_t.m_pop, m_t.m_left);
effo_memcpy(p + m_t.m_left, m_h->buf(), m_t.m_sz_left);
} else {
effo_memcpy(p, m_t.m_pop, m_t.m_sz_in);
}
m_h->sync_head()->CAS
m_h->sync_head()->CAS(m_t.m_new);
(m_t.m_new);
ret = p;
}
return ret;
}
32
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
For your conveniences, a wrapper Effo ring_buffer is provided, but here suggest using
ringed::operations directly.
template
<
class t,
template<class> class sync_policy = sync_plc_none,
template<class> class buf_creator = creator_new
>
class ring_buffer
{
protected:
typedef ringed<t, sync_policy, buf_creator, false> base_type;
typedef typename base_type::buf_creator_type buf_creator_type;
public:
typedef typename base_type::size_type size_type;
typedef typename base_type::data_type data_type;
typedef typename base_type::cell_type cell_type;
protected:
base_type m_base;
public:
inline bool init(const size_type count)
{
return m_base.init(count);
}
inline void clear(void)
{
m_base.clear();
}
33
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
10.1 push()
As mentioned ringed::operations is responsible for push() or pop() for a Ring Buffer or Ring Queue.
Below is for Ring Queue in the ringed namespace-scoped, ringed::operations
...
template<class U, bool isq>
class operations
{
// Queue type, not buffer.
protected:
typedef snapshot<U, is_q> shot_type;
typedef typename shot_type::host_type host_type;
34
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
10.2 pop()
The ringed::operations::pop()
...
inline data_type pop(void)
{
shot_type shot;
shot.host_set(m_h);
return shot.pop();
}
...
By default a ringed::operations is for Ring Queue, not for Ring Buffer.
35
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
For your conveniences, a wrapper Effo ring_queue is provided, but here suggest using
ringed::operations directly.
template
<
class t,
template<class> class sync_policy = sync_plc_none,
template<class> class buf_creator = creator_new
>
class ring_queue
{
protected:
typedef ringed<t, sync_policy, buf_creator, true> base_type;
typedef typename base_type::buf_creator_type buf_creator_type;
public:
typedef typename base_type::size_type size_type;
typedef typename base_type::data_type data_type;
typedef typename base_type::cell_type cell_type;
protected:
base_type m_base;
public:
inline bool init(const size_type count)
{
return m_base.init(count);
}
36
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
In previous sections Ring Buffer was introduced. That Ring Buffer is based on a fixed-size array or
buffer, so its size or buffer length is limited when it get initialized.
In this section, we simply use sync_queue to make a lock-free dynamic-size Ring Buffer, named
IRingBuf<DYNA>.
Before IRingBuf<DYNA>, we give other 3 lock-free queue and ring buffer first, they are
IRingQ<DYNA>, dynamic-size lock-free queue
IRingQ<STAT>, fixed-size lock-free queue
IRingBuf<STAT>, fixed-size lock-free ring buffer
All of them together with IRingBuf<DYNA> were defined on codebase/addons/lockfree of source
tree of the Effo Add-ons.
11.1 IRingQ<DYNA>
37
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
T pop(void);
Understanding them is easy because you’ve gotten sync_queue already.
An IRingQ<DYNA> is not size-limited queue type and it supports lock-free.
11.2 IRingQ<STAT>
11.3 IRingBuf<STAT>
38
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
11.4 IRingBuf<DYNA>
39
Copyright © 2008,2009 The Effo Core project. All rights reserved.
Effo Design - Lock-free Queue and Ring Buffer
http://effonetmsg.googlecode.com
http://groups.google.com/group/effonetmsg-group
Effo Devel/Framework Project & Effo Devel/Framework Group
http://effodevel.googlecode.com
http://groups.google.com/group/effodevel-group
Effo GPLed Project & Effo GPLed Group
http://effogpled.googlecode.com
http://groups.google.com/group/effogpled-group
<Nothing Following>
40
Copyright © 2008,2009 The Effo Core project. All rights reserved.