Djikstra counting semaphore in c# for your review

  • Thread starter William Stacey [MVP]
  • Start date
A

Anders Borum

Hello!

Could you give me an example (or two) of a scenario, where this kind of
class would be usefull?

This is probably the most interesting thread for days! :)
 
W

William Stacey [MVP]

1) Blocking Circular queue. Create two semaphores, one for free elements
and one for used elements. The Enqueue method grabs one slot from the free
Sem or blocks until a slot is available. The Dequeue method grabs one slot
from the used sem or blocks until a slot is available. This can make the
implementation kinda clean, but I have come to the conclusion that this is
probably not the best way to implement a blocking CQ. To do it right
requires another syncLock to protect the buffer in the middle and any other
counters. So you can end up with 5 locks being taken. One for sem enter.
One to pulse the other sem. One for syncLock in the middle, One for other
sem release and one to pulse the other sem. A two lock monitor
implementation is probably better. Actually, at the end of the day, a one
lock queue is very clean and probably better for most needs.

2) Network resource counting is a good example. Novell used a Semaphore for
years (and may still) to restrict/count the number of logins to the server.
You could use the same method for any shared service you can think of.

3) Any access to some shared data structure that you want to limit to some
count. Say you only want 5 threads to access something or only want 3
instances of your program. The Semaphore does not provide mutual exclusion
(except a binary semaphore does) so others lock(s) need to taken in right
places.

4) Limit number of email messages in an inbox on a server or local client or
limit number of SMTP server or Web clients that can "hit" the service at the
same time - others will block.

I will try to think of some more, but you could probably come up with at
least 10 new ones after thinking about 1-4 above.
 
W

William Stacey [MVP]

Update to the code? I did post a few changes after the first post. Let me
know what updates you would like to see.
 
T

TT \(Tom Tempelaere\)

William Stacey said:
Update to the code? I did post a few changes after the first post. Let me
know what updates you would like to see.

I forgot to check the link before posting, I see that you already made some
changes. Woops.

Have you already done some tests under stress? A working project that uses
the semaphore to demonstrate that it works ...

The code-documentation in AcquireAll, just before the lock on starvationLock
is a typo: it sais "... is not zero ..." but I think it should read "... is
now zero ...".

I think you should introduce Debug.Assert's at specific places for several
reasons, which is my opinion and perhaps a question of style (or QOI).
* clients of your library made a mistake in using your class/lib
* you made a mistake (aka bug) when making your class/lib
* when you refactor these can prove invaluable (I say this from experience)
* to highlight some pre-conditions or post-conditions. It looks like proof
then, and they will probably never even trigger. It helps the reviewer of
the library and yourself when you return back to the project after a long
time.
* in some libraries (eg in-house) you would not throw exceptions when
receiving bad parameters (or a bad combination), just assert. When debugging
the developer can use the code as a tool to know what he did wrong in using
the library. He should correct things if the asserts trigger and afterwards
the assert will no longer trigger. In that case the checking + throwing code
is no longer necessary and becomes code bloat. On the other hand in some
circumstances you would prefer to throw (for stability reasons), it all
depends on the type of library and who you are writing it for. MFC library
is a good example, it has a lot of asserts. A quick count of "ASSERT" (match
case, match whole word) in code of vc6 mfc library gave 5618 occurences.
Just to mention.
* Asserts could reveal nasty sneaky threading issues (because code execution
isn't linear anymore). Throwing pulls you out of the context in which the
state-error occured.

I'm not saying that this is all applicable to your class of course, but it
is something that I think is very valuable and it is something that I like
to see in library-like classes.

Another thing is that maxCount should probably be strictly greater than one.
A Mutex (or just lock) is a .Net primitive which is probably somewhat more
efficient and closer to the OS. On the other hand, you would not have the
starvation trigger though. Maybe a small docu-entry to the class ;-)?
Because reverse-sensing semaphore with maxSlots=1 will probably have its
use.

I don't understand the use for TryRelease. The implementation of Release is
almost the same, the difference is that you throw in Release and return true
or false in TryRelease. Why would someone try to release and not just
release? The way I see it, if releaseCount<=0 or if (count + releaseCount) >
maxCount then that's an error of the user of semaphore. I think you don't
need two methods that do the same thing but indicate errors in a different
way.

I think I'd prefer a TryAcquire (which is nothing more then "return
Acquire(0);") or TryAcquireAll (which is not the same as AcquireAll(0) since
all slots are acquired individually). They acquire if a slot is free (or all
slots are free) and return true if they did, otherwise they don't acquire
anything and return false.

ReleaseAll is a dangerous function now. I don't know if I would allow it to
be called if count isn't zero. That is something to think about.

In your implementation of Acquire there is a (very) small chance of
inconsistency in the face of a Thread.Interrupt call and the exception that
is a result of this. I've added some code to counter this (check below). The
same issue is present in AcquireAll.

Explanation. The exception could occur when you lock (Mutex under the hood).
If the exception occurs, the caller will think that the slot was not
acquired since the exception percolated through, but you already decremented
count when it happens.

public bool Acquire( int millisecondsTimeout )
{
lock( syncLock )
{
while( count == 0 )
try
{
if( !Monitor.Wait( syncLock, millisecondsTimeout ) )
return false;
}
catch
{
Monitor.Pulse(syncLock);
throw;
}

if( --count == 0 )
try
{
lock( starvationLock )
Monitor.PulseAll( starvationLock );
}
catch // guard against ThreadInterruptedException
{
++count;
throw;
}
return true;
}
}

I am very cautious with this because I tend to use Thread.Interrupt to wake
threads. Thread.Abort throws too but there is no good way to protect against
that of course. Anyway, ...

Cheers,
 
T

TT \(Tom Tempelaere\)

William Stacey said:
Update to the code? I did post a few changes after the first post. Let me
know what updates you would like to see.

Make maxCount readonly since you cannot change it.

Cheers,
 
W

William Stacey [MVP]

A TryRelease method just doesn't seem useful.

It is mainly some sugar. The normal Release throws exception if count is 0.
TryRelease returns false. Some times a user may just want to TryRelease
until value is false instead of catching exception and incure the exception
slowness. Or they may not know how many are available and just want to
release one or more, but not all, if they are available. It is not required
so you can remove.
BTW: I've heard rumours that .NET v2 will supply a semaphore class but I
haven't seen anything about it on the net.

I hope they do. It was a curious omission for v1. Until then, someone out
there may find this usefull, even just for learning.
 
T

TT \(Tom Tempelaere\)

William Stacey said:
It is mainly some sugar. The normal Release throws exception if count is
0.

0? Don't you mean maxCount?
TryRelease returns false. Some times a user may just want to TryRelease
until value is false instead of catching exception and incure the exception
slowness. Or they may not know how many are available and just want to

Exceptions aren't slow because they're exceptional! I don't see _any_
overhead in throwing an exception in an exceptional case. I can't think of
anyone that is going to 'try' to release for serious in production code. He
releases the slots he acquired, and shouldn't release any other. What is the
point in 'trying' if you know how many slots you acquired (of course users
of semaphore should keep track)? Releasing too many or too few is just
writing bugs IMO. If acquiring N slots was succesfull, then releasing N
slots should succeed by design. If it doesn't then there is a bug somewhere,
either in the client's code or in the library code. In that case, release
either throws an exception or you assert but I don't see the point of a
TryRelease here.
release one or more, but not all, if they are available. It is not required
so you can remove.

haven't seen anything about it on the net.

I hope they do. It was a curious omission for v1. Until then, someone out
there may find this usefull, even just for learning.

It is an interesting excercise of course.

Cheers,
 
J

Jon Skeet [C# MVP]

William Stacey said:
haven't seen anything about it on the net.

I hope they do. It was a curious omission for v1. Until then, someone out
there may find this usefull, even just for learning.

The bizarre thing is that Java hasn't had it until 1.5, either. (1.5
has a whole load of threading classes, courtesy of Doug Lea.)
 
W

William Stacey [MVP]

Exceptions aren't slow because they're exceptional! I don't see _any_
overhead in throwing an exception in an exceptional case. I can't think of

It may not be exceptional behavior is my point. It may be just the thing
you want in the fast path. You may get passed a Sem representing some
random amount of StarShips. Your code then just wants to release them out
at some interval until false (you don't know or care how many.) Or allow
button click until false. It is impossible to predict how someone may want
to use this.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads

Swiftpoint Z Mouse 2
Suggestions for the future of PC Review 50
Practicla .NET2 and C#2 7
Improving PC Review 15
Samsung NX200 Camera 0
peer to peer in C# .NET 2
Ask not 8
How to detect the real status of a socket? 4

Top