I have a summary of POSIX papers on threads, but there in no
imformation about semaphores (just conditional vars, mutexes). *NO*
pthread_semaphoreinit() etc.
In some materials, there is information on sem_wait(), sem_send() (or
sm. like that), but is it for threads (or just processes)?
It seems to me there MUST be semaphores in POSIX threads. Do I make a
mistake somewhere?
Thank you, Jan.
PS: PLEASE, could you carbon copy to pe...@hp735.cvut.cz?
--
------------------------------------------------------------------------
Jan PECHANEC | pe...@hp735.cvut.cz | http://www.ms.mff.cuni.cz/~jpec4332
------------------------------------------------------------------------
In short, You need trust old O'Reilly: "Posix.4:Programming fro the Real
World"
Its by Bill O Gallmeister isbn:1-56592-074-0.
--
Unix Software Engineer looking for C++/UNIX/Motif/Perl/Tk/Flex
Software Engineering position **NOW**. Currently in Austin at
(512) 672-5778/magr...@texas.net. have Answering machine.
Jan Pechanec wrote:
>
> It seems to me there MUST be semaphores in POSIX threads. Do I make a
> mistake somewhere?
From manual pages on SunOS about POSIX threads "man pthreads":
Semaphore
sem_init()
sem_open()
sem_close()
sem_wait()
sem_trywait()
sem_post()
sem_getvalue()
sem_unlink()
sem_destroy()
--
Richard Vanek
mailto:ri...@internet.sk
http://frdsa.utc.sk/~richo/
I hope this helps:
Jan Pechanec wrote:
> I have a summary of POSIX papers on threads, but there in no
> imformation about semaphores (just conditional vars, mutexes). *NO*
> pthread_semaphoreinit() etc.
>
> In some materials, there is information on sem_wait(), sem_send() (or
> sm. like that), but is it for threads (or just processes)?
>
> It seems to me there MUST be semaphores in POSIX threads. Do I make a
> mistake somewhere?
According to Lewis and Berg's excellent PThreads Primer, which can
be found on line at:
http://www.lambdacs.com/primer.html
On page 191 of the on-line version stating that:
``Semaphore variables are not part of the PThreads
specification, they are actually part of the POSIX realtime
specification.''
I assume that when implemented the POSIX semaphores are thread
safe.
(Note that they have a more polished hard
copy version which one can purchase called:
Multithreaded Programming with Pthreads
ISBN: 0-13-680729-1
)
Bill
This really should be fielded by Dave Butenhof, but I have been so busy
of late and haven't replied to a posting in ages -- in short, I can't
resist.
As to where are semaphores in the POSIX threads universe. The short
answer is that it "ain't".
To clarify that. Nowhere, in the standard document of POSIX
1003.1c-1995 does it say that semaphores must exist. They are instead
part of the POSIX 1003.1b Realtime extensions. Therefore, like most of
that specification there existance is "optional" even under X/Open (Unix
98).
As to the POSIX threads related functionality that would lead someone to
come to the conclusion that they "MUST be" part of Pthread. I agree
with you. In fact, sem_wait()/sem_post() are the only synchronization
calls that POSIX states can be safely used from within a signal handler.
It is, however, painfully common that a platform supports Pthread but
not POSIX semaphores. See Solaris 2.5.
Welcome to the real world.
--ben
I don't believe semaphores are part of the POSIX standard (unless things have
changed). You can, however, implement your own semaphores using condition
variables and mutexes.
-Roland
Well yes it does, but it also says (way down at the bottom in fine
print :-)):
BUGS
In Solaris 2.5, these functions always return -1 and set
errno to ENOSYS, because this release does not support the
Semaphores option. It is our intention to provide support
for these interfaces in future releases.
So they are just stubbed out calls I guess (i.e. non functional).
On a side note I've noticed that SGI does seem to have these calls
implemented, while FreeBSD does not. What is the status of other
implementations?
Bill Maniatty
Roland Mechler wrote:
>
> I don't believe semaphores are part of the POSIX standard (unless things have
> changed). You can, however, implement your own semaphores using condition
> variables and mutexes.
Maybe this can help http://www.cs.ucr.edu/~sshah/pthreads/tutorial.html
there is also a implementation of semaphore library.
I had a look at the library and it seems nice. A quick glance
seems to indicate that the semaphores in the library might be what
is called weak semaphores (as opposed to the more traditional strong
semaphores). The queuing discipline of the blocking
mechanism is the primary diference between strong and weak semaphores,
with strong semaphores having FCFS queuing, and weak semaphores having
an arbitrary (unknown?) queuing discipline. Using weak
semaphores can be challenging, particularly with regards to ensuring
fairness and avoiding both deadlock and starvation.
I'm not sure that POSIX mutexes wake up in FCFS order, if not the
semaphores presented implicitly queue on the mutexes so it seems
that the semaphores may wake up in a non FCFS order.
Bill
> Richard Vanek wrote:
...
> mechanism is the primary diference between strong and weak semaphores,
> with strong semaphores having FCFS queuing, and weak semaphores having
> an arbitrary (unknown?) queuing discipline. Using weak
> semaphores can be challenging, particularly with regards to ensuring
> fairness and avoiding both deadlock and starvation.
Your point is well taken, however I wouldn't be too quick to rule out
the use of `weak' semaphores ...
I recently implemented a semaphore library on Intel, and simply used
select() (for heavy -- fork()) processes, and/or pthread_yield() (for pmp
pthreads 1.8.5). I see no signs of starvation, and in monitoring runtime
access, each thread/process seems to be getting equal shrift ... This, of
course, is on a uni-processor ... I'm still looking for an SMP environment
to test upon 8-)
I think a careful implementation of semaphores is useful, and can be
made to work portably ... my test environment is designed to maximize
contention, and in some instances to actually generate thundering heard
conditions -- works well.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Robert S. Sciuk 1032 Howard Rd. Ph:905 632-2466
Control-Q Research Burlington, Ont. Canada Fx:905 632-7417
r...@ControlQ.com L7R 3X5 http://www.ControlQ.com
>I'm not sure that POSIX mutexes wake up in FCFS order, if not the
>semaphores presented implicitly queue on the mutexes so it seems
>that the semaphores may wake up in a non FCFS order.
The threads are queuing on the condition variable as well as the mutex,
pthread_cond_signal awakes the thread based upon it's priority and scheduling
policy. The awakened thread moves to the run queue, but doesn't necessarily
pre-empt any currently executing threads nor those of equal or higher
priority already on run queue. Since the awakened thread doesn't re-acquire
the mutex until it actually resumes execution, an intervening call by
another thread can get the semaphore ahead of it.
See http://kcgl1.eng.ohio-state.edu/~jonesd/semaphore.html for my crack
at getting a 'fair' semaphore using pthreads. Each thread that blocks
on a semaphore allocates a persistent per-thread structure that it links
to the semaphore's wait queue. Post and wait operations use the queue
to explicitly resume the waiting threads in the order they blocked.
David L. Jones | Phone: (614) 292-6929
Ohio State Unviversity | Internet:
2070 Neil Ave. Rm. 122 | jon...@kcgl1.eng.ohio-state.edu
Columbus, OH 43210 | vm...@osu.edu
Disclaimer: Dogs can't tell it's not bacon.
Are POSIX semaphores just not implemented on some Unix implementations?
If so, let me know because I'm using POSIX to be portable....
Thanks
Tim
Be careful with the general term POSIX. Usually when the term POSIX threads or
pthreads is used, what is meant is POSIX 1003.1c. The semaphore API is not
included in this specification. Semaphores are specified in the POSIX real-time
extensions, that is, POSIX 1003.1b.
The following OSs support "POSIX semaphores":
(Note: Earlier versions of these OSs may support them as well)
Solaris 2.6
HP-UX 11.0
IRIX 6.4
Digital UNIX 4.0b
AIX does NOT support "POSIX semaphores"
(there are header files, but no implementation)
(this is a real bummer)
I am unsure about Linux.
--Steve Flynn
FreeBSD 2.2.5 stable does not have POSIX Semaphores either :-(.
(Sorry to copy so much text for a one line addition).
Bill
Fortunately, they are telling lies. The sem_* operations work
fine in Solaris 2.5 (providing you link with -lposix4).
--
Andrew Gabriel
Consultant Software Engineer
and...@cucumber.demon.co.uk (Andrew Gabriel) writes:
>Fortunately, they are telling lies. The sem_* operations work
>fine in Solaris 2.5 (providing you link with -lposix4).
I'm afreaid not. The sem_* functions don't exist prior to 2.6.
There's the sema_* functions though.
Casper
--
Expressed in this posting are my opinions. They are in no way related
to opinions held by my employer, Sun Microsystems.
Statements on Sun products included here are not gospel and may
be fiction rather than truth.
Sorry, but I've been using them (sem_init, sem_post, sem_[try]wait)
since 2.5 came out, and they work perfectly without returning any
errors.
cucumber% uname -a
SunOS cucumber 5.5 Generic i86pc i386 i86pc
cucumber% nm /usr/lib/libposix4.so | grep sem_
[53] | 6684| 36|FUNC |GLOB |0 |7 |_sem_close
[70] | 6876| 87|FUNC |GLOB |0 |7 |_sem_destroy
[122] | 7224| 20|FUNC |GLOB |0 |7 |_sem_getvalue
[104] | 6756| 120|FUNC |GLOB |0 |7 |_sem_init
[54] | 6648| 36|FUNC |GLOB |0 |7 |_sem_open
[59] | 6964| 88|FUNC |GLOB |0 |7 |_sem_post
[121] | 7136| 88|FUNC |GLOB |0 |7 |_sem_trywait
[48] | 6720| 36|FUNC |GLOB |0 |7 |_sem_unlink
[78] | 7052| 84|FUNC |GLOB |0 |7 |_sem_wait
[85] | 6684| 36|FUNC |WEAK |0 |7 |sem_close
[79] | 6876| 87|FUNC |WEAK |0 |7 |sem_destroy
[92] | 7224| 20|FUNC |WEAK |0 |7 |sem_getvalue
[65] | 6756| 120|FUNC |WEAK |0 |7 |sem_init
[113] | 6648| 36|FUNC |WEAK |0 |7 |sem_open
[118] | 6964| 88|FUNC |WEAK |0 |7 |sem_post
[86] | 7136| 88|FUNC |WEAK |0 |7 |sem_trywait
[100] | 6720| 36|FUNC |WEAK |0 |7 |sem_unlink
[66] | 7052| 84|FUNC |WEAK |0 |7 |sem_wait
cucumber%
To be honest, I'd implemented using them without noticing that BUGS
section on the man page, and I probably wouldn't have bothered trying
them had I read that beforehand.
>There's the sema_* functions though.
I use these in Solaris 2.4 systems successfully.
(Also tried on Unixware, unsuccessfully.)
Maybe they exist in the 2.5 x86 version of Solaris but they sure are not
present in the Sparc version. There are stubs for them in libposix4 but
calling any sem_ functions results in error 89: Operation not applicable.
In article <6bo2nf$m...@cucumber.demon.co.uk>, and...@cucumber.demon.co.uk
says...
Bob.W...@mci.com (Bob Withers) writes:
>Maybe they exist in the 2.5 x86 version of Solaris but they sure are not
>present in the Sparc version. There are stubs for them in libposix4 but
>calling any sem_ functions results in error 89: Operation not applicable.
On inspection of the source, it turns out that prior to Solaris 2.6,
all sem_* functions return ENOSYS, except when you have linked
your program against -lthread (or -lpthread). In the latter case the
post, init, destroy, wait, trywait, getvalue, post, all call their
sema_* counterparts.
sem_open, sem_unlink, sem_close always return ENOSYS prior to 2.6
Don't know where you'd get the sem_t * to pass to sem_init, though.
Ah, wait, you just declare one yourself (and fortunately, it's exactly
liek a sema_t)
I think this whole discussion has digressed from Jan's original question
above. Yes, there are sem_* calls in Solaris 2.5 (and 2.4 IIRC); you
just
need to link with -lposix4 or whatever to get them. But these are the
*POSIX.1b* semaphores, which are *process-based* semaphores. They have
nothing to do with threads.
Now what Jan wants here is semaphore calls for *POSIX.1c*, i.e. POSIX
threads. Now, IIRC, the sem_* calls are NOT specified in POSIX.1c, but
rather
their behaviour in MT programs has been clarified/refined in XPG5
(Unix98)
which allows you to use semaphores to synchronize threads and/or
processes,
depending on how you use them.
Solaris 2.5 is not Unix98-conformant; the confusion arises because it
*does*
appear to be compliant with POSIX.1b and POSIX.1c (somebody at Sun can
surely
verify this). From what's been said here, I assume 2.6 is either Unix98-
compliant, or at least contains the MT extensions to POSIX of Unix98.
At any rate, you can't use the sem_* calls for thread synchronization in
2.5; you get ENOSYS in MT programs. I know, I've tried it (on 2.5.1).
AFAIK, single-threaded programs linked with -lposix4 work fine, but as I
said above,
they're only for process-based semaphores. So if you want to use the
sem_*
calls for thread-synchronization on Solaris, you have to go to 2.6.
OTOH: if all you need is a simple semaphore functionality without the
async-
signal-safety of sem_post(), it's fairly trivial to write your own
semaphore functionality using a simple condition variable and a mutex,
and it'll be
quite portable. All that the native sem_* stuff really buys you here is
the
ability to post a semaphore in a signal handler.
Hope this helps,
David McCann.
=============================================================
MindWork GesmbH | Mail: mccann @ mind-work . com
Frauengasse 11/2 | Voice: +43 1 21145-6262
A-1170 Wien | Fax: +43 1 489 8237-4
Austria | URL: http://www.mind-work.com/
==============================================================
[Deletia]
> I think this whole discussion has digressed from Jan's original question
> above. Yes, there are sem_* calls in Solaris 2.5 (and 2.4 IIRC); you
> just
> need to link with -lposix4 or whatever to get them. But these are the
> *POSIX.1b* semaphores, which are *process-based* semaphores. They have
> nothing to do with threads.
Thanks for clarifying that for me.
[Instructions on using semaphores in
> OTOH: if all you need is a simple semaphore functionality without the
> async-
> signal-safety of sem_post(), it's fairly trivial to write your own
> semaphore functionality.
The sem_open and sem_destroy functions have their uses too.
If you coordinate MT safe programs across different processes,
you might need to attach to a named semaphore. This comes as part
of the MT package but I'm not so sure that it is easy to implement.
Additionally if you are concerned about starvation and fairness,
many traditional semaphore solutions assume strong semaphores
(as I noted earlier) and that might requiare a bit of work.
Bill
Looks like my reference to -lposix4 was not the reason they work
then...
>On inspection of the source, it turns out that prior to Solaris 2.6,
>all sem_* functions return ENOSYS, except when you have linked
>your program against -lthread (or -lpthread). In the latter case the
Ah... -lpthread is used too, so that explains it.
>post, init, destroy, wait, trywait, getvalue, post, all call their
>sema_* counterparts.
>
>sem_open, sem_unlink, sem_close always return ENOSYS prior to 2.6
I don't use these, fortunately :-)
>Don't know where you'd get the sem_t * to pass to sem_init, though.
>Ah, wait, you just declare one yourself (and fortunately, it's exactly
>liek a sema_t)
sem_t is in /usr/include/semaphore.h on 2.5
Thanks for the explanations...
My mistake - it's the -lpthread which does it.
>*POSIX.1b* semaphores, which are *process-based* semaphores. They have
>nothing to do with threads.
They work fine with both processes and threads within processes.
That's how I'm using them. From Casper's posting, sem_open,
sem_unlink, sem_close won't work, but I've never tried those
three anyway.
>Now what Jan wants here is semaphore calls for *POSIX.1c*, i.e. POSIX
>threads.
I don't know how closely they adhere to the POSIX standards, but
they do work both multi-process and multi-thread (and in my case,
a mixture of both).
>At any rate, you can't use the sem_* calls for thread synchronization in
>2.5; you get ENOSYS in MT programs. I know, I've tried it (on 2.5.1).
Sorry, they work for me (subject to the 3 which apparently don't).
As per Casper's posting, try -lpthread
Casper,
Thanks much for this info. Unfortunately I need the semaphores for
inter-process mutual exclusion which makes sem_open important. I'll just
have to stick with SysV semaphores until we can move to 2.6.
Regards,
Bob
Pages 517-520 of the 1996 Posix.1 standard provide sample source code
that does just this, so if you have pthread mutexes and condition variables,
the semaphore functions shouldn't take more than 30 minutes to type in.
Rich Stevens
Well you sure can and, believe it or not, I actually thought of it before
I read your post. My code has not been thoroughly tested but I'm posting
it here in the hopes that it will be of help to someone else. Either
that or I'm just a glutten for criticism. :-)
Casper, thanks much for your help.
Bob
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
sem_t *sem_open(const char *name, int oflag, ...)
{
auto int need_init = 0;
auto int val = 1;
auto int fd;
auto sem_t * sem = (sem_t *) -1;
auto struct stat st;
/* -----------------2/11/98 2:12PM-------------------
* open the memory mapped file backing the shared
* semaphore to see if it exists.
* --------------------------------------------------*/
fd = open(name, O_RDWR);
if (fd >= 0)
{
/* -----------------2/11/98 2:13PM-------------------
* the semaphore already exists, it the caller
* specified O_CREAT and O_EXCL we need to return
* an error to advise them of this fact.
* --------------------------------------------------*/
if ((oflag & O_CREAT) && (oflag & O_EXCL))
{
close(fd);
errno = EEXIST;
return(sem);
}
}
else
{
auto int sem_mode;
auto va_list ap;
/* -----------------2/11/98 2:14PM-------------------
* if we get here the semaphore doesn't exist. if
* the caller did not request that ir be created then
* we need to return an error. note that errno has
* already been set appropriately by open().
* --------------------------------------------------*/
if (0 == (oflag & O_CREAT))
return(sem);
/* -----------------2/11/98 2:15PM-------------------
* ok, we're going to create a new semaphore. the
* caller should've passed mode and initial value
* arguments so we need to acquite that data.
* --------------------------------------------------*/
va_start(ap, oflag);
sem_mode = va_arg(ap, int);
val = va_arg(ap, int);
va_end(ap);
/* -----------------2/11/98 2:16PM-------------------
* create the semaphore memory mapped file. if this
* call returns an EEXIST error it means that another
* process/thread snuck in and created the semaphore
* since we discovered it doesn't exist above. we
* don't handle this condition but rather return an
* error.
* --------------------------------------------------*/
fd = open(name, O_RDWR | O_CREAT | O_EXCL, sem_mode);
if (fd < 0)
return(sem);
/* -----------------2/11/98 2:18PM-------------------
* set flag to remember that we need to init the
* semaphore and set the memory mapped file size.
* --------------------------------------------------*/
need_init = 1;
if (ftruncate(fd, sizeof(sem_t)))
{
close(fd);
return(sem);
}
}
/* -----------------2/11/98 2:19PM-------------------
* map the semaphore file into shared memory.
* --------------------------------------------------*/
sem = (sem_t *) mmap(0, sizeof(sem_t), PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
close(fd);
if (sem)
{
/* -----------------2/11/98 2:19PM-------------------
* if the mapping worked and we need to init the
* semaphore, do it now.
* --------------------------------------------------*/
if (need_init && sem_init(sem, 1, val))
{
munmap((caddr_t) sem, sizeof(sem_t));
sem = 0;
}
}
else
{
sem = (sem_t *) -1;
}
return(sem);
}
int sem_close(sem_t *sem)
{
return(munmap((caddr_t) sem, sizeof(sem_t)));
}
int sem_unlink(const char *name)
{
return(remove(name));
}
> Jan Pechanec wrote:
> >
> > Hello,
> >
> > I have a summary of POSIX papers on threads, but there in no
> > imformation about semaphores (just conditional vars, mutexes). *NO*
> > pthread_semaphoreinit() etc.
> >
> > In some materials, there is information on sem_wait(), sem_send() (or
> > sm. like that), but is it for threads (or just processes)?
>
> I think this whole discussion has digressed from Jan's original question
> above. Yes, there are sem_* calls in Solaris 2.5 (and 2.4 IIRC); you
> just need to link with -lposix4 or whatever to get them. But these are the
> *POSIX.1b* semaphores, which are *process-based* semaphores. They have
> nothing to do with threads.
>
> Now what Jan wants here is semaphore calls for *POSIX.1c*, i.e. POSIX
> threads. Now, IIRC, the sem_* calls are NOT specified in POSIX.1c, but
> rather their behaviour in MT programs has been clarified/refined in XPG5
> (Unix98) which allows you to use semaphores to synchronize threads and/or
> processes, depending on how you use them.
Not quite true. Yes, XSH5 (the "system interfaces" part of XPG5) says this; but it
does so because POSIX 1003.1-1996 says so, not because it's added something to
POSIX.
In fact, POSIX 1003.1b semaphores were designed by the same working group that did
1003.1c, and while 1003.1b-1993 was approved and published first (and therefore
couldn't mention threads), several aspects of 1003.1b were designed to work with
threads. For example, there are "realtime" extended versions of the 1003.1c
sigwait functions (sigtimedwait and sigwaitinfo). (The interfaces are slightly
incompatible because they return -1 and set errno on errors, rather than returning
an error code: that's because 1003.1c removed the use of errno AFTER 1003.1b was
finalized.)
Additionally, the sem_init function was designed with a parameter corresponding to
the "pshared" attribute of mutexes and condition variables. For 1003.1b, the only
supported value was 1, meaning "process shared". 1003.1c amended the sem_init
description to specify in addition that the value 0 meant "process private", for
use only between threads within the process. (But also note that it's perfectly
reasonable to create a "process shared" semaphore and then use it only between
threads within the process -- it may be less efficient on some implementations,
but it does the same thing.)
> Solaris 2.5 is not Unix98-conformant; the confusion arises because it
> *does* appear to be compliant with POSIX.1b and POSIX.1c (somebody at Sun can
> surely verify this). From what's been said here, I assume 2.6 is either Unix98-
> compliant, or at least contains the MT extensions to POSIX of Unix98.
Solaris 2.5 supports (most of) 1003.1b and 1003.1c, although there were a few
omissions and a few interpretation errors. (Like any other implementation.) This,
however, is not one of them. Solaris 2.5 does NOT define _POSIX_SEMAPHORES in
<unistd.h>, which is the way an implementation should advertise support for POSIX
semaphores. Therefore, while it may not implement all capabilities described by
1003.1b and 1003.1c, it doesn't (in this case, anyway) violate the standard. If
you're using POSIX semaphores (even if they seem to work) on Solaris 2.5, then
your application is not "strictly conforming", and if you're subject to any
incompatibilities or porting problems, that's your fault, not the fault of
Solaris. IT says they're not there.
(And, yes, POSIX specifically allows an implementation to provide interfaces while
claiming they're not there; and if it does so, it's not obligated to provide
strict conformance to the standard's description of those interfaces. This is what
Solaris 2.5 should have done, also, with the _POSIX_THREAD_PRIORITY_SCHEDULING
option, since it doesn't completely implement those interfaces.)
Presumably, Solaris 2.6 (though I don't have a system handy to check) DOES define
_POSIX_SEMAPHORES.
> At any rate, you can't use the sem_* calls for thread synchronization in
> 2.5; you get ENOSYS in MT programs. I know, I've tried it (on 2.5.1).
> AFAIK, single-threaded programs linked with -lposix4 work fine, but as I
> said above, they're only for process-based semaphores. So if you want to use the
>
> sem_* calls for thread-synchronization on Solaris, you have to go to 2.6.
First off, other replies have indicated that it's actually libthread, not
libposix4, that provides "working" (though not complete) POSIX semaphores. Most
likely, these semaphores would work with the "pshared" parameter set to either 0
(process) or 1 (cross-process). However, in any case, if you've got something that
can synchronize between processes, you should expect that it can synchronize
between threads as well, though there may be alternatives that are more efficient
on some implementations. (E.g., a pshared mutex will usually be more expensive to
lock or unlock than a private mutex.) (Such a difference in efficiency is less
likely for semaphores, since POSIX already requires that sem_post be async-signal
safe, which means it's far better and easier to keep the implementation inside the
kernel regardless of the pshared argument.)
/---------------------------[ Dave Butenhof ]--------------------------\
| Digital Equipment Corporation bute...@zko.dec.com |
| 110 Spit Brook Rd ZKO2-3/Q18 http://members.aol.com/drbutenhof |
| Nashua NH 03062-2698 http://www.awl.com/cseng/titles/0-201-63392-2/ |
\-----------------[ Better Living Through Concurrency ]----------------/