Using SDL_atomic

Hi,

I’m trying to use SDL_Atomic, but I don’t understand it fully. My code seems to work in different variants I tried, but it also works if I use simple integer without atomic stuff, so I’m not sure it will guard against rare race conditions. Here’s what it needs to do:

I have a game where saving takes a lot of time (it uses JSON format and has 1000+ objects, so it’s slow) and I decided to use a secondary thread. I’m using a single bool flag to mark that world is “saving”, so that main code knows not to touch some important objects and it also shows to the user when it’s done.

MAIN THREAD runs this code:

bool isSaving = false;
while (true)
{
… poll and handle sdl events (some stuff that would modify world is ignored if isSaving)
… render stuff
}

SAVE THREAD runs this code:

void save()
{
isSaving = true;
… do stuff
isSaving = false;
}

The thread is detached and gets destroyed when this function is done.

How should I convert this code to use SDL_atomic functions?

It seems to me that using SDL_atomic_t instead of bool would be straightforward:

SDL_AtomicGet(SDL_atomic_t *a);
SDL_AtomicSet(SDL_atomic_t *a, int v);

But, SDL_atomic.h says:

  • IMPORTANT:
  • If you are not an expert in concurrent lockless programming, you should
  • only be using the atomic lock and reference counting functions in this
  • file. In all other cases you should be protecting your data structures
  • with full mutexes.*
  • The list of “safe” functions to use are:
  • SDL_AtomicLock()
  • SDL_AtomicUnlock()
  • SDL_AtomicIncRef()
  • SDL_AtomicDecRef()

Can I still use SDL_AtomicGet/Set and is there something important to know?

Thanks.


bigosaur.com

To be fair an atomic here may be enough if you just want to pass
around a flag. Basically:

  1. Make the flag of type SDL_atomic_t
  2. Do SDL_AtomicSet(&flag, 0) and then spawn the secondary thread
    3a) In the main thread, do SDL_AtomicGet(&flag) until it doesn’t return 0
    3b) In the secondary thread, do SDL_AtomicSet(&flag, 1) when it’s done saving
  3. Let the secondary thread die :stuck_out_tongue:

(swap 0 and 1 if you think that makes more sense, the point stands)

Atomics are enough when you’re doing stuff like signaling the status
of something through a flag. If it’s data that’s constantly being
shared among threads then a mutex is better. (a good example of this
is the audio callback in SDL, changing anything that the callback uses
requires using a mutex)

http://wiki.libsdl.org/SDL_AtomicSet needs code as wellOn Sat, Feb 28, 2015 at 5:10 AM, Sik the hedgehog < sik.the.hedgehog at gmail.com> wrote:

To be fair an atomic here may be enough if you just want to pass
around a flag. Basically:

  1. Make the flag of type SDL_atomic_t
  2. Do SDL_AtomicSet(&flag, 0) and then spawn the secondary thread
    3a) In the main thread, do SDL_AtomicGet(&flag) until it doesn’t return 0
    3b) In the secondary thread, do SDL_AtomicSet(&flag, 1) when it’s done
    saving
  3. Let the secondary thread die :stuck_out_tongue:

(swap 0 and 1 if you think that makes more sense, the point stands)

Atomics are enough when you’re doing stuff like signaling the status
of something through a flag. If it’s data that’s constantly being
shared among threads then a mutex is better. (a good example of this
is the audio callback in SDL, changing anything that the callback uses
requires using a mutex)


SDL mailing list
SDL at lists.libsdl.org
http://lists.libsdl.org/listinfo.cgi/sdl-libsdl.org

Hi,

I think your proposal will have a race condition like this:

// main thread
if (!saving) // atomic read of saving here
{
// Problem: saving thread might ‘saving’ to true right now
// main thread modifies world while saving thread is reading it =>
corrupted save
}

To make it safe, you should use a lock/mutex. Just from skimming the
SDL_Lock documentation, and assuming you want the main thread to skip
the world update during saving, I’d do it like this:

// main thread is going to update world
if (SDL_AtomicTryLock (lock)) // if the saving thread is currently
holding the lock, don’t wait for it, just skip updating the world
{
// update world here
SDL_AtomicUnlock(lock);
}

// saving thread.
SDL_AtomicLock(lock); // If the main thread is currently updating
world, this will (correctly) wait until the lock is released.
// read world state and save it
SDL_AtomicUnlock(lock);

The thing to watch out for here is you have to be 100% sure the
SDL_AtomicUnlock calls are made,
i.e. watch out for exceptions / gotos / break statements / etc. - if
you miss unlocking for some reason, you’ll get a deadlock (or else the
world will stop updating.)

Hope this helps.
EricOn Tue, Feb 24, 2015 at 2:46 PM, mbabuskov <milan.babuskov at gmail.com> wrote:

Hi,

I’m trying to use SDL_Atomic, but I don’t understand it fully. My code seems
to work in different variants I tried, but it also works if I use simple
integer without atomic stuff, so I’m not sure it will guard against rare
race conditions. Here’s what it needs to do:

I have a game where saving takes a lot of time (it uses JSON format and has
1000+ objects, so it’s slow) and I decided to use a secondary thread. I’m
using a single bool flag to mark that world is “saving”, so that main code
knows not to touch some important objects and it also shows to the user when
it’s done.

MAIN THREAD runs this code:

bool isSaving = false;
while (true)
{
… poll and handle sdl events (some stuff that would modify world is
ignored if isSaving)
… render stuff
}

SAVE THREAD runs this code:

void save()
{
isSaving = true;
… do stuff
isSaving = false;
}

The thread is detached and gets destroyed when this function is done.

How should I convert this code to use SDL_atomic functions?

It seems to me that using SDL_atomic_t instead of bool would be
straightforward:

SDL_AtomicGet(SDL_atomic_t *a);
SDL_AtomicSet(SDL_atomic_t *a, int v);

But, SDL_atomic.h says:

  • IMPORTANT:
  • If you are not an expert in concurrent lockless programming, you should
  • only be using the atomic lock and reference counting functions in this
  • file. In all other cases you should be protecting your data structures
  • with full mutexes.
  • The list of “safe” functions to use are:
  • SDL_AtomicLock()
  • SDL_AtomicUnlock()
  • SDL_AtomicIncRef()
  • SDL_AtomicDecRef()

Can I still use SDL_AtomicGet/Set and is there something important to know?

Thanks.

im gonna attempt to update all the wiki when someone gives me a login and a
laptop ( laptop tomorrow ) a lot that is missing from the page is in the
code tests just need ctrl c & v , unless someone has written an elaborate
scriptOn Sat, Feb 28, 2015 at 1:52 PM, Eric Wasylishen wrote:

On Tue, Feb 24, 2015 at 2:46 PM, mbabuskov <milan.babuskov at gmail.com> wrote:

Hi,

I’m trying to use SDL_Atomic, but I don’t understand it fully. My code
seems
to work in different variants I tried, but it also works if I use simple
integer without atomic stuff, so I’m not sure it will guard against rare
race conditions. Here’s what it needs to do:

I have a game where saving takes a lot of time (it uses JSON format and
has
1000+ objects, so it’s slow) and I decided to use a secondary thread. I’m
using a single bool flag to mark that world is “saving”, so that main
code
knows not to touch some important objects and it also shows to the user
when
it’s done.

MAIN THREAD runs this code:

bool isSaving = false;
while (true)
{
… poll and handle sdl events (some stuff that would modify world is
ignored if isSaving)
… render stuff
}

SAVE THREAD runs this code:

void save()
{
isSaving = true;
… do stuff
isSaving = false;
}

The thread is detached and gets destroyed when this function is done.

How should I convert this code to use SDL_atomic functions?

It seems to me that using SDL_atomic_t instead of bool would be
straightforward:

SDL_AtomicGet(SDL_atomic_t *a);
SDL_AtomicSet(SDL_atomic_t *a, int v);

But, SDL_atomic.h says:

  • IMPORTANT:
  • If you are not an expert in concurrent lockless programming, you should
  • only be using the atomic lock and reference counting functions in this
  • file. In all other cases you should be protecting your data structures
  • with full mutexes.
  • The list of “safe” functions to use are:
  • SDL_AtomicLock()
  • SDL_AtomicUnlock()
  • SDL_AtomicIncRef()
  • SDL_AtomicDecRef()

Can I still use SDL_AtomicGet/Set and is there something important to
know?

Thanks.

Hi,

I think your proposal will have a race condition like this:

// main thread
if (!saving) // atomic read of saving here
{
// Problem: saving thread might ‘saving’ to true right now
// main thread modifies world while saving thread is reading it =>
corrupted save
}

To make it safe, you should use a lock/mutex. Just from skimming the
SDL_Lock documentation, and assuming you want the main thread to skip
the world update during saving, I’d do it like this:

// main thread is going to update world
if (SDL_AtomicTryLock (lock)) // if the saving thread is currently
holding the lock, don’t wait for it, just skip updating the world
{
// update world here
SDL_AtomicUnlock(lock);
}

// saving thread.
SDL_AtomicLock(lock); // If the main thread is currently updating
world, this will (correctly) wait until the lock is released.
// read world state and save it
SDL_AtomicUnlock(lock);

The thing to watch out for here is you have to be 100% sure the
SDL_AtomicUnlock calls are made,
i.e. watch out for exceptions / gotos / break statements / etc. - if
you miss unlocking for some reason, you’ll get a deadlock (or else the
world will stop updating.)

Hope this helps.
Eric


SDL mailing list
SDL at lists.libsdl.org
http://lists.libsdl.org/listinfo.cgi/sdl-libsdl.org

To be fair an atomic here may be enough if you just want to pass
around a flag.

Not really. Atomic operations ensure that reads and writes
of the data return a coherent value. That’s all.

So if you increment a variable atomically in thread 1,
thread 2 will either read the old value, or it will read
the new value. It won’t read a messed up value even if the
variable is multibyte.

A mutex is just a user defined atomic operation.
So in principle it also cannot be used to “pass” a value.

The best way to synchronise threads is to use channels.
[Library like 0MQ provides this, as does my Felix system]

Unfortunately these primitives are not available on most
processors and not available in most programming languages
either.

So you will have to use the next best thing, which is a Posix
semaphore. Semaphores synchronise threads by allowing
one thread to set some data then signal another thread
it has done so. The other thread can wait for the signal.

General semaphores are of course not useful for
passing data though.

Posix therefore had to enhance the concept to include
fencing, that is, to ensure data to be communicated at
the synchronisation time are shared.

Unfortunately, there’s no way to know what is to be shared
so EVERYTHING has to be synchronised.

This is true of a Posix mutex too.

Anyhow, semaphores are the way to go with Posix.
Windows doesn’t have them, but there are emulations
available. No idea about iOS or Android.On 01/03/2015, at 12:10 AM, Sik the hedgehog wrote:


john skaller
@john_skaller
http://felix-lang.org

2015-02-28 20:02 GMT-03:00, john skaller :

Not really. Atomic operations ensure that reads and writes
of the data return a coherent value. That’s all.

So if you increment a variable atomically in thread 1,
thread 2 will either read the old value, or it will read
the new value. It won’t read a messed up value even if the
variable is multibyte.

Flags are booleans though (???)

2015-02-28 20:02 GMT-03:00, john skaller <@john_skaller>:

Not really. Atomic operations ensure that reads and writes
of the data return a coherent value. That’s all.

So if you increment a variable atomically in thread 1,
thread 2 will either read the old value, or it will read
the new value. It won’t read a messed up value even if the
variable is multibyte.

Flags are booleans though (???)

That’s true but the encoding could be 64 bit 0
or (int64_1)(-1) = 0xFFFFFFFFFFFFFFFF.

Without an atomic operation one might set the second value
but read

0xFFFFFFFF00000000

and who knows what the effect would be on the application?
[Assuming the data bus is only 32 bit :]On 01/03/2015, at 1:35 PM, Sik the hedgehog wrote:


john skaller
@john_skaller
http://felix-lang.org

2015-03-01 0:41 GMT-03:00, john skaller :

That’s true but the encoding could be 64 bit 0
or (int64_1)(-1) = 0xFFFFFFFFFFFFFFFF.

Without an atomic operation one might set the second value
but read

0xFFFFFFFF00000000

and who knows what the effect would be on the application?
[Assuming the data bus is only 32 bit :]

Yeah, but I was talking about doing it with SDL_AtomicSet/Get which
are atomic (also IIRC the underlying type is 32-bit signed integer
anyway). For a case like this a full-blown mutex is overkill, mutexes
are designed for sharing around larger amounts of data rather than
just a flag.

eric.w wrote:

I think your proposal will have a race condition like this:

// main thread
if (!saving) // atomic read of saving here
{
// Problem: saving thread might ‘saving’ to true right now
// main thread modifies world while saving thread is reading it =>
corrupted save

I fail to see how can this create problems. The saving thread sets “saving” to true just before quitting. It won’t try to read world anymore. The next time save is done, a new saving thread will be started.

But, I see other potential problems:

  1. it’s possible that changed value is not propagated to the main thread quickly (or ever). As far as I researched this on the Internet, it seems that this should never happen on common modern platforms (Intel CPUs where this game will run), but it could in theory. AFAICT, using SDL_SetAtomic would fix this.

  2. The saving thread code is:

void save()
{
… save stuff
saveFlag = false;
}

As I understand, some compiler/CPU might “optimize” the code and since saveFlag is not checked or used in the whole save() function it could reorder the code to make that statement run before the “safe stuff” or somewhere in the middle of it.

I changed the code to use SDL_AtomicSet.

void save()
{
… save stuff
SDL_AtomicSet(&saveFlag, 0);
}

Would that make sure that code is not reordered? Is there some chance that a compiler or CPU could reorder my SDL_AtomicSet call to run before “save stuff”?------------------------
bigosaur.com

Actually, to clarify. My code looks like this now:

MAIN THREAD:

while (!gameOver)
{

int isSaving = SDL_AtomicGet(&saveFlag);
if (needsSaving && !isSaving)
{
needsSaving = false;
SDL_AtomicSet(&saveFlag, 1);
start detached save thread
}

if (!isSaving)
updateDangerousStuff();

updateOtherStuff();
}

SAVE THREAD:

void save()
{
…save stuff to file

SDL_AtomicSet(&saveFlag, 0);

...thread gets destroyed

}

The saving thread never sets the flag to true, only to false.------------------------
bigosaur.com

  1. it’s possible that changed value is not propagated to the main thread quickly (or ever). As far as I researched this on the Internet, it seems that this should never happen on common modern platforms (Intel CPUs where this game will run), but it could in theory. AFAICT, using SDL_SetAtomic would fix this.

How?

Atomic write and read ensures that the individual writes of
the write will not interleave with individual reads of the read.

Nothing more. It makes no assurances about the ordering
of any OTHER reads and writes by any threads.

You need a fence for that. So consider:

var saving = false;

proc game() {
while run do
if needs_saving do
saving = true;
spawn_pthread saving_thread;
done
if not saving do dangerous_stuff; done
safe_stuff;
done
}

proc saving_thread () {
save_data;
saving = false;
}

Assuming the assignments are atomic, is this code safe?
[BTW: this is real Felix code]

The answer, unfortunately is NO. The reason is, some writes
scheduled in dangerous_stuff might not have occurred when
saving thread is launched. In fact they might propagate in pieces
during the save operation.

To force the writes to occur, you need a fence before saving is set
to true. This ensures the writes are completed before saving commences.
Of course they will appear completed to the main thread without a fence.

Generally synchronisation primitives include a fence, or the primitives
would be useless. However if you check Posix pthread_mutex_lock
specification, for example, there’s no mention of it. Nor any of the SDL_Atomic
functions.On 01/03/2015, at 9:16 PM, mbabuskov wrote:


john skaller
@john_skaller
http://felix-lang.org

john skaller wrote:

How?
javascript:bbstyle(-1)

Whoops, you are right. Actually, I missed one line from my code. Here’s what it really looks like:

MAIN THREAD:

while (!gameOver)
{

int isSaving = SDL_AtomicGet(&saveFlag);
if (needsSaving && !isSaving)
{
needsSaving = false;
isSaving = 1; // the line missing from the last message
SDL_AtomicSet(&saveFlag, 1);
start detached save thread
}

if (!isSaving)
updateDangerousStuff();

updateOtherStuff();
}

SAVE THREAD:

void save()
{
…save stuff to file

SDL_AtomicSet(&saveFlag, 0);

…thread gets destroyed
}------------------------
bigosaur.com

Message-ID:

Content-Type: text/plain; charset=us-ascii

Atomic write and read ensures that the individual writes of
the write will not interleave with individual reads of the read.

Nothing more. It makes no assurances about the ordering
of any OTHER reads and writes by any threads.

You need a fence for that. So consider:

Assuming the assignments are atomic, is this code safe?
[BTW: this is real Felix code]

The answer, unfortunately is NO. The reason is, some writes
scheduled in dangerous_stuff might not have occurred when
saving thread is launched. In fact they might propagate in pieces
during the save operation.

To force the writes to occur, you need a fence before saving is set
to true. This ensures the writes are completed before saving commences.
Of course they will appear completed to the main thread without a fence.

Generally synchronisation primitives include a fence, or the primitives
would be useless. However if you check Posix pthread_mutex_lock
specification, for example, there’s no mention of it. Nor any of the SDL_Atomic
functions.

John, look at this documentation page: https://wiki.libsdl.org/CategoryAtomic

Right above the Atomic Locks header it has this line: “All of the
atomic operations that modify memory are full memory barriers.”

This probably isn’t the highest-performance way to do things (there
might be something that does fences according to cache block: you’d
get better performance while inside a protected region if you used a
fence as rarely as possible while inside that block), but it’s the
best that can be done both outside of the compiler, AND without the
programmer digging into the hairy details of their target processor.> Date: Mon, 2 Mar 2015 01:00:33 +1100

From: john skaller
To: sdl at lists.libsdl.org
Subject: Re: [SDL] Using SDL_atomic

Generally synchronisation primitives include a fence, or the primitives
would be useless. However if you check Posix pthread_mutex_lock
specification, for example, there’s no mention of it. Nor any of the SDL_Atomic
functions.

John, look at this documentation page: https://wiki.libsdl.org/CategoryAtomic

Right above the Atomic Locks header it has this line: “All of the
atomic operations that modify memory are full memory barriers.”

Ah, thanks. This comment should be repeated in the documentation
for each individual function.

i will fix it!On 02/03/2015, at 6:57 AM, Jared Maddox wrote:


john skaller
@john_skaller
http://felix-lang.org

Generally synchronisation primitives include a fence, or the primitives
would be useless. However if you check Posix pthread_mutex_lock
specification, for example, there’s no mention of it. Nor any of the SDL_Atomic
functions.

John, look at this documentation page: https://wiki.libsdl.org/CategoryAtomic

Right above the Atomic Locks header it has this line: “All of the
atomic operations that modify memory are full memory barriers.”

Ah, thanks. This comment should be repeated in the documentation
for each individual function.

i will fix it!

[I have started doing this but my bandwidth is so bad I can’t load web pages in a reasonable time at the moment … will have to finish it later when bandwidth improves. Sorry!]On 02/03/2015, at 10:46 AM, john skaller wrote:

On 02/03/2015, at 6:57 AM, Jared Maddox wrote:


john skaller
@john_skaller
http://felix-lang.org

john skaller writes:

  1. it’s possible that changed value is not propagated to the main thread quickly (or ever). As far as I researched this on the Internet, it seems that this should never happen on common modern platforms (Intel CPUs where this game will run), but it could in theory. AFAICT, using SDL_SetAtomic would fix this.

How?

Atomic write and read ensures that the individual writes of
the write will not interleave with individual reads of the read.

Nothing more. It makes no assurances about the ordering
of any OTHER reads and writes by any threads.
[…]
To force the writes to occur, you need a fence before saving is set
to true. This ensures the writes are completed before saving commences.
Of course they will appear completed to the main thread without a fence.

Generally synchronisation primitives include a fence, or the primitives
would be useless. However if you check Posix pthread_mutex_lock
specification, for example, there’s no mention of it. Nor any of the SDL_Atomic
functions.

To quote the documentation: “All of the atomic operations that modify
memory are full memory barriers.”

Though, now that I look at it, I realize that SDL_AtomicGet() does not,
in fact, “modify” memory. So there might be a problem there.

And for pthread_mutex_lock():
http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_11

“functions that synchronize thread execution and also synchronize memory
with respect to other threads” … includes the pthread_mutex functions.

Though of course, we have the little issue that “threads cannot be
implemented as a library”:
http://www.hpl.hp.com/techreports/2004/HPL-2004-209.pdf

In practice, this doesn’t matter so much, since all current compilers
will do the right thing. For obvious reasons :slight_smile:

eirik> On 01/03/2015, at 9:16 PM, mbabuskov wrote:

2015-03-02 14:14 GMT-03:00, Eirik Byrkjeflot Anonsen :

Though, now that I look at it, I realize that SDL_AtomicGet() does not,
in fact, “modify” memory. So there might be a problem there.

But SDL_AtomicSet still would, so the worst that would happen is that
SDL_AtomicGet would just see the update later than expected if I
understand correctly (but will eventually see it, since it’s actively
asking for the data in RAM). When SDL_AtomicGet sees the change it’s
safe to assume that whatever happened before SDL_AtomicSet made its
way into RAM already.

The problem would be if you keep making changes after SDL_AtomicSet,
but in that case you should ask yourself how do you expect the code to
work in the first place :stuck_out_tongue:

Sik the hedgehog <sik.the.hedgehog at gmail.com> writes:

2015-03-02 14:14 GMT-03:00, Eirik Byrkjeflot Anonsen <@Eirik_Byrkjeflot_Ano>:

Though, now that I look at it, I realize that SDL_AtomicGet() does not,
in fact, “modify” memory. So there might be a problem there.

But SDL_AtomicSet still would, so the worst that would happen is that
SDL_AtomicGet would just see the update later than expected if I
understand correctly (but will eventually see it, since it’s actively
asking for the data in RAM). When SDL_AtomicGet sees the change it’s
safe to assume that whatever happened before SDL_AtomicSet made its
way into RAM already.

You would think so, however:

void protected_dangerous() {
if (SDL_AtomicGet(atomic) == 0) {
dangerous(shared_data);
}
}

Looks safe, right? However, if SDL_AtomicGet() is not a proper memory
barrier, the compiler is allowed to rewrite this as:

void protected_dangerous() {
suitable_type tmp_shared_data = shared_data;
if (SDL_AtomicGet(atomic) == 0) {
dangerous(tmp_shared_data);
}
}

Note that in this case ‘shared_data’ is read before ‘atomic’ is tested.
Thus it might end up sending a stale value to dangerous(). When the SDL
documentation says “Seriously, here be dragons!”, it really means it :slight_smile:

eirik

You would think so, however:

void protected_dangerous() {
if (SDL_AtomicGet(atomic) == 0) {
dangerous(shared_data);
}
}

Looks safe, right? However, if SDL_AtomicGet() is not a proper memory
barrier, the compiler is allowed to rewrite this as:

void protected_dangerous() {
suitable_type tmp_shared_data = shared_data;
if (SDL_AtomicGet(atomic) == 0) {
dangerous(tmp_shared_data);
}
}

Note that in this case ‘shared_data’ is read before ‘atomic’ is tested.
Thus it might end up sending a stale value to dangerous(). When the SDL
documentation says “Seriously, here be dragons!”, it really means it :slight_smile:

SDL_AtomicGet takes a pointer to an
struct SDL_Atomic_t which contains an integer field named “value”.
Here is the current definition:

typedef struct { int value; } SDL_atomic_t;

I think this should read:

typedef struct {int volatile value; } SDL_atomic_t;

That’s line 189

https://hg.libsdl.org/SDL/file/bc00287b414f/include/SDL_atomic.h

Then the “volatile” should, normally, prevent any compiler optimisations.

[It’s not assured by the C or C++ Standards but a compiler breaking normally
expected volatile semantics would be a poor implementation … not that that’s
saying much, given how recent gcc’s break a lot of previously working code
in the name of strict adherence to a Standard semantics for a language
which is sloppy and weak … bad idea GNU!]

I have to say, however, that these notes in the same file would be much
simpler to use with explicit fences instead of crudding about with
undocumented and uncertain features:

/**

  • Memory barriers are designed to prevent reads and writes from being
  • reordered by the compiler and being seen out of order on multi-core CPUs.On 03/03/2015, at 7:13 AM, Eirik Byrkjeflot Anonsen wrote:
  • A typical pattern would be for thread A to write some data and a flag,
  • and for thread B to read the flag and get the data. In this case you
  • would insert a release barrier between writing the data and the flag,
  • guaranteeing that the data write completes no later than the flag is
  • written, and you would insert an acquire barrier between reading the
  • flag and reading the data, to ensure that all the reads associated
  • with the flag have completed.
  • In this pattern you should always see a release barrier paired with
  • an acquire barrier and you should gate the data reads/writes with a
  • single flag variable.
  • For more information on these semantics, take a look at the blog post:
  • http://preshing.com/20120913/acquire-and-release-semantics
    */


john skaller
@john_skaller
http://felix-lang.org