mirror of
https://github.com/Alcaro/Flips.git
synced 2026-09-03 08:24:04 -05:00
66 lines
2.6 KiB
C++
66 lines
2.6 KiB
C++
#include "thread.h"
|
|
|
|
#ifdef ARLIB_THREAD
|
|
//a nonatomic read to an atomic variable is safe only if correct results are guaranteed if any old value is read
|
|
//a write of non-NULL and non-tag is guaranteed to be the final write, and if anything else seems to be there, we do an atomic read
|
|
void* thread_once_undo_core(void* * item, function<void*()> calculate, function<void(void*)> undo)
|
|
{
|
|
if (*item) return *item;//nonatomic - if something weird happens, all that happens is that another item is created and deleted.
|
|
void* obj = calculate();
|
|
void* prev = lock_cmpxchg(item, NULL, obj);
|
|
if (prev == NULL) return obj;
|
|
else
|
|
{
|
|
undo(obj);
|
|
return prev;
|
|
}
|
|
}
|
|
|
|
#ifndef __linux__
|
|
static event* contention_unlocker=NULL;
|
|
|
|
#if 1 //if NULL==0 and points to a permanently reserved area of at least 3 bytes (the limit is 65536 on all modern OSes)
|
|
#define MAKE_TAG(n) ((void*)(n+1))
|
|
#else //assume sizeof(obj*)>=2 - no other thread can return this, they don't know where it is
|
|
#define MAKE_TAG(n) (void*)(((char*)&contention_unlocker)+n)
|
|
#endif
|
|
#define tag_busy MAKE_TAG(0)
|
|
#define tag_contended MAKE_TAG(1)
|
|
|
|
//Bug: If two objects are simultaneously initialized by two threads each, then one of the objects may hold up the other.
|
|
//This is not fixable without borrowing at least one bit from the item, which we don't want to do; alternatively waking all waiters, which can't be done either.
|
|
void* thread_once_core(void* * item, function<void*()> calculate)
|
|
{
|
|
void* check=*item;
|
|
//common case - initialized already
|
|
//not using an atomic read because stale values are fine, they're caught by the cmpxchg
|
|
if (check != NULL && check != tag_busy && check != tag_contended) return check;
|
|
|
|
void* old = lock_cmpxchg(item, NULL, tag_busy);
|
|
if (old == NULL)
|
|
{
|
|
void* result = calculate();
|
|
//'written' is either tag_busy or tag_contended here.
|
|
//It's not NULL because we wrote tag_busy, and it can't be anything else
|
|
// because the other threads know that they're only allowed to replace it with tag_contended.
|
|
if (lock_cmpxchg(item, tag_busy, result) == tag_contended)
|
|
{
|
|
thread_once_create(&contention_unlocker);
|
|
lock_write(item, result);
|
|
contention_unlocker->signal();
|
|
}
|
|
}
|
|
else if (old == tag_busy || old == tag_contended)
|
|
{
|
|
//don't bother optimizing this, contention only happens a few times during program lifetime
|
|
lock_cmpxchg(item, tag_busy, tag_contended);
|
|
thread_once_create(&contention_unlocker);
|
|
while (lock_read(item) == tag_busy) contention_unlocker->wait();
|
|
contention_unlocker->signal();
|
|
}
|
|
//it's possible to hit neither of the above if the object was written between the initial read and the swap
|
|
return *item;
|
|
}
|
|
#endif
|
|
#endif
|