Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Mutex with Spin-Count

115 views
Skip to first unread message

Bonita Montero

unread,
May 12, 2020, 4:49:58 PM5/12/20
to
Here's again the mutex with adjustable spin-count an the benchmark:

C++:

#if defined(_MSC_VER)
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#elif defined(__unix__)
#include <semaphore.h>
#endif
#include <iostream>
#include <cstdint>
#include <cstdlib>
#include <vector>
#include <thread>
#include <random>
#include <atomic>
#include <csignal>

#pragma warning(disable: 6031)
#pragma warning(disable: 6387)
#pragma warning(disable: 26495)

using namespace std;

struct Semaphore
{
Semaphore();
void release();
void wait();
private:
#if defined(_MSC_VER)
HANDLE hSem;
#elif defined(__unix__)
sem_t sem;
#endif
};

inline
Semaphore::Semaphore()
#if defined(_MSC_VER)
: hSem( CreateSemaphore( nullptr, 0, 0x7FFFFFFF, nullptr ) )
#endif
{
#if defined(__unix__)
sem_init( &sem, 0, 0 );
#endif
}

inline
void Semaphore::release()
{
#if defined(_MSC_VER)
ReleaseSemaphore( hSem, 1, nullptr );
#elif defined(__unix__)
sem_post( &sem );
#endif
}

inline
void Semaphore::wait()
{
#if defined(_MSC_VER)
WaitForSingleObject( hSem, INFINITE );
#elif defined(__unix__)
sem_wait( &sem );
#endif
}

struct SpinMutex
{
SpinMutex( uint32_t spinCount );
void lock();
void unlock();
uint64_t getAndResetSpinSuceeds();
uint64_t getAndResetSpinFails();
private:
uint32_t spinCount;
atomic<uint32_t> lockCounter;
Semaphore sem;
atomic<uint64_t> spinSucceeds,
spinFails;
uint64_t getAndZero( atomic<uint64_t> &value );
};

SpinMutex::SpinMutex( uint32_t spinCount ) :
spinCount( spinCount ),
lockCounter( 0 ),
spinSucceeds( 0 ),
spinFails( 0 )
{
}

void SpinMutex::lock()
{
for( uint32_t sc = spinCount; sc; --sc )
{
uint32_t cmp = 0;
if( lockCounter.compare_exchange_weak( cmp, 1, memory_order_acquire,
memory_order_relaxed ) )
{
if( --sc ) // only count if it is not the first attempt to lock the mutex
spinSucceeds.fetch_add( 1, memory_order_relaxed );
return;
}
}
if( spinCount ) // only count fails if there's a spin-count
spinFails.fetch_add( 1, memory_order_relaxed );
if( lockCounter.fetch_add( 1, memory_order_acquire ) != 0 )
sem.wait();
}

void SpinMutex::unlock()
{
if( lockCounter.fetch_sub( 1, memory_order_release ) != 1 )
sem.release();
}

inline
uint64_t SpinMutex::getAndResetSpinSuceeds()
{
return getAndZero( spinSucceeds );
}

inline
uint64_t SpinMutex::getAndResetSpinFails()
{
return getAndZero( spinFails );
}

inline
uint64_t SpinMutex::getAndZero( atomic<uint64_t> &value )
{
uint64_t ref = value;
while( !value.compare_exchange_weak( ref, 0 ) );
return ref;
}

void spendCycles( uint64_t cycles );

#if defined(__unix__)
void spendCycles( uint64_t cycles )
{
uint64_t volatile c = cycles;
while( c-- );
}
#endif

atomic<bool> stop;

int main( int argc, char **argv )
{
char const *errStr = "1. number of threads, 2. min non locked cycles,
3. max non locked cycles, 4. min locked cycles, 5. max locked cycles, 6.
spin-count";
if( argc < 7 )
{
cout << errStr << endl;
return EXIT_FAILURE;
}
unsigned nThreads;
unsigned long long minNonLockedCycles, maxNonLockedCycles;
unsigned long long minLockedCycles, maxLockedCycles;
unsigned spinCount;
sscanf( argv[1], "%u", &nThreads );
sscanf( argv[2], "%llu", &minNonLockedCycles );
sscanf( argv[3], "%llu", &maxNonLockedCycles );
sscanf( argv[4], "%llu", &minLockedCycles );
sscanf( argv[5], "%llu", &maxLockedCycles );
sscanf( argv[6], "%u", &spinCount );
if( nThreads == 0 || minNonLockedCycles > maxNonLockedCycles ||
minLockedCycles > maxLockedCycles )
{
cout << errStr << endl;
return EXIT_FAILURE;
}
auto sigHandler = []( int sig ) -> void
{
::stop = true;
signal( SIGINT, SIG_IGN );
};
::stop = false;
signal( SIGINT, sigHandler );
SpinMutex sm( spinCount );
auto thr = [&]()
{
random_device rd;
minstd_rand mr( rd() );
uniform_int_distribution<uint64_t> uidNonLocked( minNonLockedCycles,
maxNonLockedCycles );
uniform_int_distribution<uint64_t> uidLocked( minLockedCycles,
maxLockedCycles );
while( !stop.load( memory_order_relaxed ) )
spendCycles( uidNonLocked( mr ) ),
sm.lock(),
spendCycles( uidLocked( mr ) ),
sm.unlock();
};
vector<thread> threads;
for( unsigned t = 0; t != nThreads; ++t )
threads.emplace_back( thr );
while( !stop.load( memory_order_relaxed ) )
{
this_thread::sleep_for( 1s );
cout << "succeeds: " << sm.getAndResetSpinSuceeds() << endl;
cout << "fails: " << sm.getAndResetSpinFails() << endl;
}
for( thread &t : threads )
t.join();
}

Asm:

PUBLIC ?spendCycles@@YAX_K@Z
_TEXT SEGMENT
?spendCycles@@YAX_K@Z PROC
test rcx, rcx
jz byebye
cycleLoop:
dec rcx ; roughly one jump per clock-cycle
jnz cycleLoop ; because there's only one branch-unit
byebye:
ret
?spendCycles@@YAX_K@Z ENDP
_TEXT ENDS
END

The code was initially written for Windows but now I wrote a primitive
semaphore-class for POSIX and Windows to make the code compilable under
both platform. Unfortunately the spendCycles code isn't timing-equiva-
lent under Windows and Linux. With g++ and my Ryzen 1800X each iteration
takes 8 cycles whereas with Windows and MASM each iteration takes one
cycle. Maybe someone could supply a some inline-assembly-code; I'm not
familiar with inline-assembly with g++.
The code spawns a configurable number of threads which alternately call
spendCycles while not holding the common mutex and while holding it.
The intervals are chosen from a random range given with a lower and
upper bound as command-line parameters. The number of threads is given
as the first parameter, the second parameter is the lower bound of the
non-locked interval the third parameter is the upper bound, the fourth
is the lower bound of the locked interval, the fifth is its upper bound
and the sixth is the spin-count of the mutex.
The program altenately outputs the number of succeeded and failed spins.
The succeeded spins are only counted if there has been more than one
attempt to aquire the lock. The fail-counter is only raised if there
is a spin-counter != 0.

Chris M. Thomasson

unread,
May 12, 2020, 4:51:40 PM5/12/20
to
On 5/12/2020 1:49 PM, Bonita Montero wrote:
> Here's again the mutex with adjustable spin-count an the benchmark:
[...]

Thank you! Okay, will bust out Relacy and port it. Sometime today, or
tomorrow.

Bonita Montero

unread,
May 12, 2020, 5:06:21 PM5/12/20
to
>> Here's again the mutex with adjustable spin-count an the benchmark:
> [...]

> Thank you! Okay, will bust out Relacy and port it.
> Sometime today, or tomorrow.

Sorry, that's like debugging hello world. Read the code and you'll
see in 10min that is correct (aside from non-handled resource-erors).

Chris M. Thomasson

unread,
May 12, 2020, 5:08:19 PM5/12/20
to
Na. I read the code in detail during the porting process.

Bonita Montero

unread,
May 12, 2020, 5:10:27 PM5/12/20
to
>> Sorry, that's like debugging hello world. Read the code and you'll
>> see in 10min that is correct (aside from non-handled resource-erors).

> Na. I read the code in detail during the porting process.

Which porting-process ?
The code is compilable for Windows and Linux.

Bonita Montero

unread,
May 12, 2020, 5:14:35 PM5/12/20
to
>> Sorry, that's like debugging hello world. Read the code and you'll
>> see in 10min that is correct (aside from non-handled resource-erors).

> Na. I read the code in detail during the porting process.

I just think that if you add additional code, the timing of
the spinning will change, that Öö Tiib could get into trouble
to prove what he assumes.

Chris M. Thomasson

unread,
May 12, 2020, 5:22:54 PM5/12/20
to
On 5/12/2020 2:10 PM, Bonita Montero wrote:
>>> Sorry, that's like debugging hello world. Read the code and you'll
>>> see in 10min that is correct (aside from non-handled resource-erors).
>
>> Na. I read the code in detail during the porting process.
>
> Which porting-process ?

To create a Relacy test unit out of it.

Chris M. Thomasson

unread,
May 12, 2020, 5:23:12 PM5/12/20
to
Huh?

Chris M. Thomasson

unread,
May 12, 2020, 6:36:51 PM5/12/20
to
On 5/12/2020 1:49 PM, Bonita Montero wrote:
[...]

Fwiw, this is a bug. You are misusing sem_wait. Keep in mind that this
is suppose to be a general purpose mutex. Relacy tests for this wrt
sem_wait, and it will report data-race for your code.

https://pubs.opengroup.org/onlinepubs/007908799/xsh/sem_wait.html

sem_wait can return EINTR. This means that the function must be tried again.

Btw, this is an old school bug wrt failing to check the return value of
sem_wait. Relacy will simulate this condition. Noticed it as I am
porting the Semaphore::wait functions.

Chris M. Thomasson

unread,
May 12, 2020, 6:38:49 PM5/12/20
to
I should say that sem_wait returns -1 and errno can be EINTR.

Chris M. Thomasson

unread,
May 12, 2020, 6:50:05 PM5/12/20
to
On 5/12/2020 1:49 PM, Bonita Montero wrote:
[...]

Relacy also reports resource leaks. You never destroy the Semaphore.



Chris M. Thomasson

unread,
May 12, 2020, 7:37:06 PM5/12/20
to
I finally implemented the Windows version of the mutex using a Relacy
test unit. It works fine. However, the POSIX version needs to properly
handle sem_wait return values wrt errno, Relacy simulates this and will
report a deadlock. I omitted all of the spin counting code you had and
just stuck to the main lock logic. Take a look at the code and make sure
I got everything right. Pretty sure I did. I had to add a destructor to
the Semaphore class to close the handle. Relacy reports a memory leak if
this is not accomplished.


Btw, the semaphore logic you use i the mutex itself is basically the
same as a benaphore:


https://www.haiku-os.org/legacy-docs/benewsletter/Issue1-26.html#Engineering1-26


Fwiw, here is the test code:

/*
Mutex with Spin Count
Algorithm by Bonita
Test by Chris M. Thomasson
_____________________________________________________*/


//#define RL_DEBUGBREAK_ON_ASSERT
//#define RL_MSVC_OUTPUT
//#define RL_FORCE_SEQ_CST
//#define RL_GC


#include <relacy/relacy_std.hpp>
#include <iostream>
#include <vector>
#include <algorithm>


#define CT_THREADS 3


struct Semaphore
{
Semaphore();
~Semaphore(); // dtor
void release();
void wait();
private:
HANDLE hSem;
};


Semaphore::Semaphore()
: hSem(CreateSemaphore(nullptr, 0, 0x7FFFFFFF, nullptr))
{}


Semaphore::~Semaphore()
{
CloseHandle(hSem);
}


void Semaphore::release()
{
ReleaseSemaphore(hSem, 1, nullptr);
}

void Semaphore::wait()
{
WaitForSingleObject(hSem, INFINITE);
}




struct SpinMutex
{
std::atomic<unsigned long> lockCounter;
unsigned long spinCount;

Semaphore sem;

SpinMutex(unsigned long spincount)
: lockCounter(0), spinCount(spincount)
{}

void lock()
{
for (unsigned long sc = spinCount; sc; --sc)
{
unsigned long cmp = 0;

if (lockCounter.compare_exchange_weak(
cmp,
1,
std::memory_order_acquire
)) {
return;
}
}

if (lockCounter.fetch_add(1, std::memory_order_acquire) != 0)
{
// slow path
sem.wait();
}
}

void unlock()
{
if (lockCounter.fetch_sub(1, std::memory_order_release) != 1)
{
// slow path
sem.release();
}
}
};


struct ct_experiment
: rl::test_suite<ct_experiment, CT_THREADS>
{
SpinMutex g_lock;

VAR_T(unsigned long) g_state;


ct_experiment() : g_lock(7), g_state(0) {}


void thread(unsigned int tidx)
{
//std::cout << "thread: " << tidx << "\n";

g_lock.lock();
VAR(g_state) += 1;
g_lock.unlock();
}
};


// Test away... Or fly? Humm...
int main()
{
{
rl::test_params p;

p.iteration_count = 10000000;
//p.execution_depth_limit = 33333;
//p.search_type = rl::sched_bound;
//p.search_type = rl::fair_full_search_scheduler_type;
//p.search_type = rl::fair_context_bound_scheduler_type;

rl::simulate<ct_experiment>(p);
}

return 0;
}

Bonita Montero

unread,
May 13, 2020, 2:08:48 AM5/13/20
to
> Fwiw, this is a bug. You are misusing sem_wait. ...

No.

Bonita Montero

unread,
May 13, 2020, 2:09:32 AM5/13/20
to
> I should say that sem_wait returns -1 and errno can be EINTR.

That's experimental code. EINTR normally doesn't happen.

Bonita Montero

unread,
May 13, 2020, 2:14:07 AM5/13/20
to
> I finally implemented the Windows version of the mutex using a Relacy
> test unit. It works fine. However, the POSIX version needs to properly
> handle sem_wait return values wrt errno,

Boy, you're so strupid. That's not industrial-level code for relase to
a product; so there's no need to handle EINTR.

> I had to add a destructor to the Semaphore class to close the handle.

Also not necessary for the purpose of this code, that's while I skipped
it.

red floyd

unread,
May 13, 2020, 2:15:44 AM5/13/20
to
So anyone who disagrees with you is stupid. Bye bye... *PLONK*

Bonita Montero

unread,
May 13, 2020, 2:17:14 AM5/13/20
to
>> Also not necessary for the purpose of this code, that's while I skipped
>> it.

> So anyone who disagrees with you is stupid. Bye bye... *PLONK*

His objections simply have nothing to do with the question discussed,
i.e. whether adaptive mutexes make sense.

Chris M. Thomasson

unread,
May 13, 2020, 2:18:28 AM5/13/20
to
On 5/12/2020 11:09 PM, Bonita Montero wrote:
>> I should say that sem_wait returns -1 and errno can be EINTR.
>
> That's experimental code. EINTR normally doesn't happen.
>

lol. Okay... Whatever. Anyway, your code works in a Relacy test unit. :^)

Chris M. Thomasson

unread,
May 13, 2020, 2:19:43 AM5/13/20
to
The test unit makes me have to add the dtor to your code. Also, it makes
me have to make your code handle EINTR from sem_wait. It actually
simulates that.

Chris M. Thomasson

unread,
May 13, 2020, 2:20:06 AM5/13/20
to
On 5/12/2020 11:08 PM, Bonita Montero wrote:
>> Fwiw, this is a bug. You are misusing sem_wait. ...
>
> No.
>

Yes.

Bonita Montero

unread,
May 13, 2020, 2:20:45 AM5/13/20
to
> lol. Okay... Whatever. Anyway, your code works in a Relacy test unit. :^)

Sorry, there's no need for Relacy here because the mutex is simple.

Chris M. Thomasson

unread,
May 13, 2020, 2:21:15 AM5/13/20
to
Porting your code over to Relacy makes these corrections necessary to
pass a test. You failed to close the handle to the semaphore, and failed
to handle EINTR. Well, shi% happens.

Chris M. Thomasson

unread,
May 13, 2020, 2:21:45 AM5/13/20
to
On 5/12/2020 11:20 PM, Bonita Montero wrote:
>> lol. Okay... Whatever. Anyway, your code works in a Relacy test unit. :^)
>
> Sorry, there's no need for Relacy here because the mutex is simple.

It found a memory leak and a dead lock condition wrt EINTR.

Bonita Montero

unread,
May 13, 2020, 2:27:39 AM5/13/20
to
> Porting your code over to Relacy makes these corrections necessary to
> pass a test.

It wasn't my idea to add Relacy-code here. The code is trivial and
you can see withoud debugging that it is correct for the discussed
purpose. that you use this tool here makes me strongly doubt you.

> You failed to close the handle to the semaphore, and failed
> to handle EINTR. ...

I didn't think about EINTR but that doesn't matter in this case.
And the other was intenionally.


Bonita Montero

unread,
May 13, 2020, 2:29:04 AM5/13/20
to
>> Sorry, there's no need for Relacy here because the mutex is simple.

> It found a memory leak and a dead lock condition wrt EINTR.

You're such a mega-idiot. That's not industrial-level code, it's
expermental to allow to prove Öö Tiibs assumptions. And for this
puprose it is completely adequate.

Bonita Montero

unread,
May 13, 2020, 3:11:06 AM5/13/20
to
So everything is fine now:

#if defined(_MSC_VER)
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#pragma warning(disable: 6031)
#pragma warning(disable: 6387)
#pragma warning(disable: 26495)
#elif defined(__unix__)
#include <semaphore.h>
#endif
#include <iostream>
#include <cstdint>
#include <cstdlib>
#include <vector>
#include <thread>
#include <random>
#include <atomic>
#include <csignal>

using namespace std;

struct Semaphore
{
Semaphore();
void release();
void wait();
private:
int main( int argc, char **argv )
{
char const *errStr = "1. number of threads, 2. min non locked cycles,
3. max non locked cycles, 4. min locked cycles, 5. max locked cycles, 6.
spin-count";
if( argc < 7 )
{
cout << errStr << endl;
return EXIT_FAILURE;
}
unsigned nThreads;
unsigned long long minNonLockedCycles, maxNonLockedCycles;
unsigned long long minLockedCycles, maxLockedCycles;
unsigned spinCount;
sscanf( argv[1], "%u", &nThreads );
sscanf( argv[2], "%llu", &minNonLockedCycles );
sscanf( argv[3], "%llu", &maxNonLockedCycles );
sscanf( argv[4], "%llu", &minLockedCycles );
sscanf( argv[5], "%llu", &maxLockedCycles );
sscanf( argv[6], "%u", &spinCount );
if( nThreads == 0 || minNonLockedCycles > maxNonLockedCycles ||
minLockedCycles > maxLockedCycles )
{
cout << errStr << endl;
return EXIT_FAILURE;
}
SpinMutex sm( spinCount );
auto thr = [&]()
{
random_device rd;
minstd_rand mr( rd() );
uniform_int_distribution<uint64_t> uidNonLocked( minNonLockedCycles,
maxNonLockedCycles );
uniform_int_distribution<uint64_t> uidLocked( minLockedCycles,
maxLockedCycles );
while( !stop.load( memory_order_relaxed ) )
spendCycles( uidNonLocked( mr ) ),
sm.lock(),
spendCycles( uidLocked( mr ) ),
sm.unlock();
};
vector<thread> threads;
for( unsigned t = 0; t != nThreads; ++t )
threads.emplace_back( thr );
for( ; ; )
{
this_thread::sleep_for( 1s );
cout << "succeeds: " << sm.getAndResetSpinSuceeds() << endl;
cout << "fails: " << sm.getAndResetSpinFails() << endl;
}
for( thread &t : threads )
t.join();
}

I don't handle SIGINT anymore but sigint terminates the program.
So I don't have to care for EINTR. And closing the semaphore isn't
necessary anyway.

Bonita Montero

unread,
May 13, 2020, 8:10:24 AM5/13/20
to
Compile the following like this:
g++ -O3 -pthread SpinMutex.cpp spendCycles.s

SpinMutex.cpp :
for( ; ; )
spendCycles( uidNonLocked( mr ) ),
sm.lock(),
spendCycles( uidLocked( mr ) ),
sm.unlock();
};
vector<thread> threads;
for( unsigned t = 0; t != nThreads; ++t )
threads.emplace_back( thr );
for( ; ; )
{
this_thread::sleep_for( 1s );
cout << "succeeds: " << sm.getAndResetSpinSuceeds() << endl;
cout << "fails: " << sm.getAndResetSpinFails() << endl;
}
for( thread &t : threads )
t.join();
}

spendCycles.s :

.text
.globl _Z11spendCyclesm
.type _Z11spendCyclesm, @function
_Z11spendCyclesm:
.LFB0:
.cfi_startproc
test %rdi, %rdi
jz byebye
cycleLoop:
dec %rdi
jnz cycleLoop
byebye:
ret
.cfi_endproc

Now the code does the same under Windows and under Linux and the timing
-loop is comparable. Öö Tiib canh compile it and give the parameters
that show if spinnnig makes sense; but I guess he can't.

Alf P. Steinbach

unread,
May 13, 2020, 9:37:03 AM5/13/20
to
If it doesn't need to be correct it can be arbitrarily simple and fast.

- Alf

Bonita Montero

unread,
May 13, 2020, 10:03:04 AM5/13/20
to
>> You're such a mega-idiot. That's not industrial-level code, it's
>> expermental to allow to prove Öö Tiibs assumptions. And for this
>> puprose it is completely adequate.

> If it doesn't need to be correct it can be arbitrarily simple and fast.

It wouldn't be slower if it would be correct.

Chris M. Thomasson

unread,
May 13, 2020, 7:28:27 PM5/13/20
to
On 5/13/2020 5:10 AM, Bonita Montero wrote:
> Compile the following like this:
> g++ -O3 -pthread SpinMutex.cpp spendCycles.s
[...]

I have not tried to compile it with g++ yet. I just ported it over to
Relacy simply because the porting process is a nice way to learn the
algorithm, and it helps flush out buggers. No matter how short it is.

The main point is that the lock logic itself works fine.

Chris M. Thomasson

unread,
May 13, 2020, 7:29:18 PM5/13/20
to
That is a strange comment to me. Humm...

Chris M. Thomasson

unread,
May 14, 2020, 3:49:16 PM5/14/20
to
You never would have thought about EINTR.

chad altenburg

unread,
May 14, 2020, 10:42:03 PM5/14/20
to
FIGHT FIGHT

TAKE IT OFF

JERRY JERRY

Bonita Montero

unread,
May 14, 2020, 11:26:55 PM5/14/20
to
> You never would have thought about EINTR.

That's not necessary here because EINTR terminates
the program when you don't catch ^C.

red floyd

unread,
May 15, 2020, 12:48:19 AM5/15/20
to
I'm glad you never write programs that catch SIGINT.
Other people, however, do write them.

red floyd

unread,
May 15, 2020, 12:49:02 AM5/15/20
to
On 5/14/20 8:26 PM, Bonita Montero wrote:
I'm also glad you don't write programs in non-canonical
mode *cough*ncurses*cough*

Bonita Montero

unread,
May 15, 2020, 2:06:22 AM5/15/20
to
> I'm glad you never write programs that catch SIGINT.

I've written such programs an they handle it properly.
But here that's not necessary.

red floyd

unread,
May 15, 2020, 1:09:01 PM5/15/20
to
So you know that NOBODY in the world who may wish to use your mutex will
EVER write a program that traps SIGINT. Damn, I wish I was as psychic
as you.

Bonita Montero

unread,
May 15, 2020, 1:19:57 PM5/15/20
to
>> I've written such programs an they handle it properly.
>> But here that's not necessary.

> So you know that NOBODY in the world who may wish to use your mutex will
> EVER write a program that traps SIGINT.

The mutex is not a general-purpose mutex; it is only to prove Öö
Tiib's assumptions - or not. Therefore it doesn't need to care
for SIGINT, resource-collapse or destruction.

> EVER write a program that traps SIGINT.
> Damn, I wish I was as psychic as you.

I'm not psychic, but you're stupid.

Bonita Montero

unread,
May 15, 2020, 1:23:03 PM5/15/20
to
If you really need a decent semaphore-class, here's one I've written as
I wrote my lock-free LRU-algorithm:

#pragma once
#if defined(_MSC_VER)
#include <Windows.h>
#include <intrin.h>
#elif defined(__unix__)
#include <semaphore.h>
#include <pthread.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/stat.h>
#include <climits>
#endif
#include <new>
#include <cstdint>
#include <cassert>
#include <system_error>

// bug: handle EINTR

class semaphore
{
public:
semaphore();
semaphore( semaphore const & ) = delete;
void operator =( semaphore const & ) = delete;
~semaphore();
bool wait();
void forced_wait();
unsigned release( unsigned count = 1 );
void forced_release( unsigned count = 1 );

private:
#if defined(_MSC_VER)
HANDLE m_hSem;
#elif defined(__unix__)
#if defined(SYSV_SEMAPHORE)
union semun
{
int val;
semid_ds *buf;
unsigned short *array;
seminfo *__buf;
};
int m_semid;
#else
sem_t m_sem;
#endif
#else
#error need platform-specific semaphore!
#endif
};

#if defined(_MSC_VER)

inline
semaphore::semaphore()
{
using namespace std;
// semaphore-creation failed ?
if( (m_hSem = CreateSemaphore( nullptr, 0, 0x7FFFFFFF, nullptr )) == NULL )
// yes: throw system_error
throw std::system_error( error_code( (int)GetLastError(),
system_category() ), "creating semaphore failed" );
}

inline
semaphore::~semaphore()
{
// close semaphore
BOOL success = CloseHandle( m_hSem );
// debugging: check success
assert(success);
}

inline
bool semaphore::wait()
{
// wait for semaphore
bool success = WaitForSingleObject( m_hSem, INFINITE ) == WAIT_OBJECT_0;
// debugging: check success
assert(success);
// return success or error
return success;
}

inline
unsigned semaphore::release( unsigned count )
{
// increment semaphore by one
BOOL success = ReleaseSemaphore( m_hSem, (LONG)count, nullptr );
// debugging: check success
assert(success);
// return number of successful releases
return success ? 0 : count;
}

#elif defined(__unix__)

inline
semaphore::semaphore()
{
#if defined(SYSV_SEMAPHORE)
// sysv-semaphore-creation failed ?
if( (m_semid = semget( IPC_PRIVATE, 1, IPC_CREAT | S_IRWXU )) == -1 )
// yes: throw system_error
throw std::system_error( error_code( ::errno, system_category() ),
"creating semaphore failed" );
// set created sysv-semaphore to zero
semun su;
su.val = 0;
// ... failed ?
if( semctl( m_semid, 0, SETVAL, su ) == -1 )
{
int errno = ::errno;
// yes: destroy sysv-semaphore
int ret = semctl( m_semid, 0, IPC_RMID );
// debugging: break if destruction failed
assert(ret != -1);
// throw system_error
throw std::system_error( error_code( ::errno, system_category() ),
"setting semaphore to zero after creation failed" );
}
#else
// posix-semaphore-creation failed ?
if( sem_init( &m_sem, 0, 0 ) != 0 )
// yes: throw system_error
throw std::system_error( error_code( ::errno, system_category() ),
"creating semaphore failed" );
#endif
}

inline
semaphore::~semaphore()
{
#if defined(SYSV_SEMAPHORE)
// destroy sysv-semaphore
int ret = semctl( m_semid, 0, IPC_RMID );
// debugging: break if destruction failed
assert(ret != -1);
#else
// destroy posix-semaphore
int ret = sem_destroy( &m_sem );
// debugging: break if destruction failed
assert(ret == 0);
#endif
}

inline
bool semaphore::wait()
{
#if defined(SYSV_SEMAPHORE)
// wait for sysv-semaphore
sembuf sb;
sb.sem_op = -1;
sb.sem_num = 0;
sb.sem_flg = 0;
bool success = semop( m_semid, &sb, 1 ) == 0;
// debugging: break waiting failed
assert(success);
// return true on success, false on error
return success;
#else
// wait for posix-semaphore
bool success = sem_wait( &m_sem ) == 0;
// debugging: break waiting failed
assert(success);
// return true on success, false on error
return success;
#endif
}

inline
unsigned semaphore::release( unsigned count )
{
#if defined(SYSV_SEMAPHORE)
// sysv-semaphore: make SHRT_MAX relases per step
for( unsigned short release; (release = (unsigned short)(count %
SHRT_MAX)) != 0; count -= release )
{
// release-step for sysv-semaphore
sembuf sb;
sb.sem_op = (short)release;
sb.sem_num = 0;
sb.sem_flg = 0;
// release successful ?
if( semop( m_semid, &sb, 1 ) != 0 )
{
// no: debug-break
assert(false);
// return remaining number of releases
return count;
}
}
// all releases successful; return zero
return 0;
#else
// release posix-semaphore count times
for( ; count; --count )
// single release failed ?
if( sem_post( &m_sem ) != 0 )
{
// yes: debug-break
assert(false);
// return remaining number of releases
return count;
}
return 0;
#endif
}

#else
#error need platform-specific semaphore
#endif

inline
void semaphore::forced_wait()
{
// loop until wait is successful
while( !wait() );
}

inline
void semaphore::forced_release( unsigned count )
{
// release semaphore count times until successful
for( ; count; count = release( count ) );
}

red floyd

unread,
May 15, 2020, 7:30:39 PM5/15/20
to
On 5/15/20 10:19 AM, Bonita Montero wrote:

>
> I'm not psychic, but you're stupid.
>

ad hominem. You lose.

Keith Thompson

unread,
May 15, 2020, 9:07:34 PM5/15/20
to
She wins as long as people keep replying.

--
Keith Thompson (The_Other_Keith) Keith.S.T...@gmail.com
Working, but not speaking, for Philips Healthcare
void Void(void) { Void(); } /* The recursive call of the void */

Jorgen Grahn

unread,
May 16, 2020, 1:59:53 AM5/16/20
to
On Sat, 2020-05-16, Keith Thompson wrote:
> red floyd <no....@its.invalid> writes:
>> On 5/15/20 10:19 AM, Bonita Montero wrote:
>>> I'm not psychic, but you're stupid.
>>
>> ad hominem. You lose.
>
> She wins as long as people keep replying.

"What a strange game. The only winning move is not to play."
-- WOP, "War Games"

/Jorgen

--
// Jorgen Grahn <grahn@ Oo o. . .
\X/ snipabacken.se> O o .

Chris M. Thomasson

unread,
May 16, 2020, 6:12:40 PM5/16/20
to
Fwiw, I remember way back about a STM that had to handle SIGSEGV's and
perform rollbacks. Something like:

https://www.kernel.org/doc/Documentation/powerpc/transactional_memory.txt

Bonita Montero

unread,
May 17, 2020, 12:15:54 PM5/17/20
to
> Fwiw, I remember way back about a STM that had to handle SIGSEGV's and
> perform rollbacks. Something like:
> https://www.kernel.org/doc/Documentation/powerpc/transactional_memory.txt

1. That's not about STM but HTM of POWER8/9.
2. I think the word signal here doesn't mean a Unix-signal but something
like a CPU-interrupt; in end-effect this could lead to a Unix-signal,
but I don't believe that this was primarily meant here.
With the HTM / TSX of the Intel-CPUs interrupts also roll back
transactions and this also incorporates signals which pass through
the kernel. So I think that's the same here with the POWER-CPUs.

Bonita Montero

unread,
May 17, 2020, 12:17:34 PM5/17/20
to
>> That's not necessary here because EINTR terminates
>> the program when you don't catch ^C.

> I'm also glad you don't write programs in non-canonical
> mode *cough*ncurses*cough*

I know when EINTR-handling is appropriate and when not.
You don't.

Chris M. Thomasson

unread,
May 17, 2020, 4:09:03 PM5/17/20
to
On 5/17/2020 9:15 AM, Bonita Montero wrote:
>> Fwiw, I remember way back about a STM that had to handle SIGSEGV's and
>> perform rollbacks. Something like:
>> https://www.kernel.org/doc/Documentation/powerpc/transactional_memory.txt
>
> 1. That's not about STM but HTM of POWER8/9.

Iirc, several STM's had to handle SIGSEGV's.

[...]

red floyd

unread,
May 17, 2020, 10:59:08 PM5/17/20
to
I'm so glad you know that my 38 years of Unix programming experience
haven't taught me anything...

Screw you.

Bonita Montero

unread,
May 18, 2020, 12:42:30 AM5/18/20
to
>> I know when EINTR-handling is appropriate and when not.
>> You don't.

> I'm so glad you know that my 38 years of Unix programming
> experience haven't taught me anything...

According what you said there wasn't any real experience.

Bonita Montero

unread,
May 18, 2020, 4:51:47 AM5/18/20
to
>>> Fwiw, I remember way back about a STM that had to handle SIGSEGV's
>>> and perform rollbacks. Something like:
>>> https://www.kernel.org/doc/Documentation/powerpc/transactional_memory.txt

>> 1. That's not about STM but HTM of POWER8/9.

> Iirc, several STM's had to handle SIGSEGV's.

Yes, it was years I go when I wrote a lockfree structure which
was safeguarded via SEH / EXCEPTION_ACCESS_VIOLATION. I think
it was you who said that this wasn't portable.

Chris M. Thomasson

unread,
May 18, 2020, 4:40:30 PM5/18/20
to
SEH is a Windows thing, and it works well for these types of scenarios.
It can be portable via SIGSEGV. Fwiw, iirc, the Windows lock-free stack
uses it in a very clever way for its lock-free stack:

https://docs.microsoft.com/en-us/windows/win32/sync/using-singly-linked-lists

Iirc, it uses it for the function that removes an item from the list.

Chris M. Thomasson

unread,
May 18, 2020, 4:44:41 PM5/18/20
to
This is a classic problem with a lock-free stack. Iirc, the IBM freepool
documentation says that the memory used for the nodes should not be
deallocated. You can get around it using RCU. However, SEH works pretty
good.

Chris M. Thomasson

unread,
May 18, 2020, 4:46:03 PM5/18/20
to
On 5/18/2020 1:40 PM, Chris M. Thomasson wrote:
Sorry for all of the posts, but, iirc, the Windows slist's even had
special support from the kernel.

0 new messages