462 30.ACrossPlatformMultithreadingFramework
int sem;
int hash = CalcHashFromString("SemaphoreName");
for (;;)
{
// Create the semaphore.
if ((sem = semget(p_Hash, 3, 0666 | IPC_CREAT)) == -1)
THROW_LAST_UNIX_ERROR();
// Lock the newly created semaphore set.
if (semop(sem, OpBeginCreate,
sizeof(OpBeginCreate) / sizeof(sembuf)) >= 0)
break;
if (errno != EINVAL && errno != EIDRM)
THROW_LAST_UNIX_ERROR();
}
Listing 30.1. System V semaphore creation.
existed. In case a new semaphore set was created, we set the semaphore value to
p_InitialValue and the semaphore reference count to a large integer value (we
cannot count up from zero because we use this state to determine whether the set
has been initialized). Finally, we decrement our reference counter and release the
lock on semaphore 2 to complete the initialization.
In Listings 30.1 and 30.2, we can also see the power of the
semop() function.
Beside the semaphore set ID, this function also expects an array of semaphore
operations. Each entry in this array is of type
sembuf and has the data fields de-
scribed in Table 30.1.
2
int semval;
if ((semval = semctl(sem, 1, GETVAL, 0)) < 0)
THROW_LAST_UNIX_ERROR();
// If semaphore 1's value is 0 the set has not been initialized.
if (semval == 0)
2
See also The Single UNIX Specification, Version 2, available at http://opengroup.org/
onlinepubs/007908799/xsh/semop.html.
30.2SynchronizationObjects 463
{
// Initialize semaphore value.
if (semctl(sem, 0, SETVAL, p_InitialValue) < 0)
THROW_LAST_UNIX_ERROR();
// Init semaphore object references counter.
if (semctl(sem, 1, SETVAL, MaxSemObjectRefs) < 0)
THROW_LAST_UNIX_ERROR();
}
// Decrement reference counter and unlock semaphore set.
if (semop(sem, OpEndCreate, sizeof(OpEndCreate) / sizeof(sembuf)) < 0)
THROW_LAST_UNIX_ERROR();
Listing 30.2. System V semaphore initialization.
The operation member specifies the value that should be added to the sema-
phore counter (or subtracted in case it is negative). If we subtract a value that is
greater than the semaphore’s current value, then the function suspends execution
of the calling thread. When we are using an operation value of zero,
semop()
checks whether the given semaphore’s value is zero. If it is, the function returns
immediately, otherwise, it suspends execution. Additionally, there are two flags
that can be specified with each operation,
IPC_NOWAIT and SEM_UNDO. If
IPC_NOWAIT is specified, then semop() always returns immediately and never
blocks. Each operation performed with
SEM_UNDO is recorded internally in the
semaphore set. In case the program terminates unexpectedly, the kernel uses this
information to reverse all effects of the recorded operations. Using this mecha-
nism, we avoid any situation in which a semaphore remains locked by a dead
process.
Member Description
sem_num Identifies a semaphore within the current semaphore set.
sem_op Semaphore operation.
sem_flg Operation flags.
Table 30.1. System V semaphore operation fields.
464 30.ACrossPlatformMultithreadingFramework
Cleanup of our semaphore set works in a way similar to its creation. First, we
acquire the lock of our binary semaphore with ID 2 and increment the reference
count of semaphore 1. Then, we compare the current value of semaphore 1 to the
constant integer we used to initialize it. If they have the same value, then we can
go ahead and delete the semaphore from the system using
semctl() with the
IPC_RMID flag. If the values do not match, we know that there are still references
to the semaphore set, and we proceed by freeing our lock on semaphore 2.
Our implementation of
BasicSemaphore makes use of both POSIX and Sys-
tem V semaphores for various reasons. For named semaphores, we use the Sys-
tem V API because of its
SEM_UNDO feature. While developing the framework, it
so happened that an application with a locked POSIX semaphore crashed. After
restarting the application and trying to obtain a lock to the same semaphore, we
became deadlocked because the lock from our previous run was still persisting.
This problem doesn’t arise when we use System V semaphores because the ker-
nel undoes all recorded operations and frees the lock when an application crash-
es. A problem that both solutions still suffer from is that semaphore objects
remain alive in case of an application crash since there is no automatic cleanup
performed by the operating system. POSIX semaphores are used for process-
internal communication because they have a little smaller overhead than private
System V semaphores.
Event(classBasicEvent)
The last type of synchronization mechanism we implemented in our framework
is the event. An event can have two different states, signaled or nonsignaled. In a
common scenario, a thread waits for an event until another thread sets its state to
signaled. If a thread waits for an event and receives a signal, then the event may
remain signaled; or in case of an auto-reset event, the event is returned to the
nonsignaled state. This behavior is slightly different from the Pthreads counter-
part, the condition variable. A condition variable has two functions to set its state
to signaled. The first,
pthread_cond_signal(), unblocks at least one waiting
thread; the other,
pthread_cond_broadcast(), unblocks all waiting threads. If a
thread is not already waiting when the signal is sent, it simply misses that signal
and blocks. Another difference with the Windows API is that each condition var-
iable must be used together with a Pthreads mutex. Before a thread is able to wait
on a condition variable, it first has to lock the associated mutex. The thread is
granted the ownership of this mutex after it receives a signal and leaves the
pthread_cond_wait() function.
Again, our implementation orients along the Windows API, and functions
such as
Wait(), Set(), and Reset() simply call WaitForSingleObject(),
30.2SynchronizationObjects 465
SetEvent(), and ResetEvent(). To support other platforms, we once more have
two different implementation paths. Because Pthreads provides no support for
system-wide condition variables, we emulate this scenario using a System V
semaphore. The other implementation uses a Pthread condition variable in con-
junction with a boolean flag indicating the state of the event.
Let us now take a closer look at the Pthreads implementation, starting with
the
Set() method. After acquiring the lock on our mutex, we set the signaled
flag to true and continue by signaling the condition variable. If our event has
manual reset enabled, then we use
pthread_cond_broadcast() to resume all
threads waiting on our condition variable. In the other case, we use
pthread_cond_signal() to wake up exactly one waiting thread.
Pseudocode for the implementation of the
Wait() method, when specifying
an infinite timeout, is illustrated in Listing 30.3. After locking the mutex, we
check the state of the signaled flag. If it is true, we can leave the wait function
immediately. If we do not use a manual reset event, then the signal flag is reset to
false before leaving the method. In case the event state is nonsignaled, we enter a
loop that keeps waiting on the condition variable until the event state changes to
signaled or an error occurs.
Lock Mutex
if signal flag set to true
if manual reset disabled
Set signal flag to false
Unlock Mutex and return true
Loop infinitely
Wait on Condition Variable
if return code is 0
if signal flag set to true
if manual reset disabled
Set signal flag to false
Unlock Mutex and return true
else
Handle error
End Loop
Unlock Mutex
Listing 30.3. Pseudocode for waiting for an event.
466 30.ACrossPlatformMultithreadingFramework
The pseudocode in Listing 30.3 is one of three separate cases that require
distinct handling, depending on the timeout value passed to the
Wait() method.
It shows the execution path when passing an infinite timeout value. If we use a
timeout value of zero, then we just test the current signal state and return imme-
diately. In this case, we only execute the first block and skip the loop. If neither
zero nor infinite is used as the timeout value, then we can use the same code as in
the listing with two minor changes. First, we use
pthread_cond_timedwait() to
wait on the condition variable with a given timeout. The second adaption is that
our
Wait() method has to return false in case pthread_cond_timedwait() re-
turns
ETIMEDOUT.
The last method, which is rather easy to implement, is used to set an event’s
state to nonsignaled and is called
Reset(). All we need to do is to obtain the lock
on our mutex and set the event’s signal state to false.
Thanks to the flexibility of System V semaphores, we can easily rebuild the
functionality of an event using a binary semaphore. Construction and cleanup is
performed exactly as described for the
BasicSemaphore class. The value of our
semaphore is directly mapped to the event state—a value of one indicates non-
signaled and a value of zero represents a signaled state. The
Set() method is a
simple call to
semop() that decrements the semaphore value. We also specify the
IPC_NOWAIT flag to avoid blocking in case the event is already in the signaled
state. If the semaphore value is zero, then the
semop() call simply fails with EA-
GAIN
, which means that Set() can be called multiple times without any unex-
pected side effects. The
Reset() method works almost as simply, but uses two
operations. The first operation checks whether the event is currently in signaled
state using the
IPC_NOWAIT flag. In case the semaphore value is zero, the first
operation succeeds and performs the second operation, which increments the val-
ue. In case the event is already reset, the semaphore value is already one, and the
first operation fails with
EAGAIN. In this case, the second operation is not execut-
ed, which ensures the coherence of our event status in the semaphore.
The implementation of the
Wait() method for our semaphore-based event is
also quite easy. Again, we have three different scenarios, depending on the speci-
fied timeout value. In order to check whether the event is in signaled state, we
use
semop() with either one or two operations, depending on whether the event
is a manual reset event. The first operation simply checks whether the semaphore
value equals zero, with or without (zero or infinite timeout specified, respective-
ly) using the
IPC_NOWAIT flag. If we are not using a manual reset event, we in-
crement the semaphore value after testing for zero, thus setting the state back to
nonsignaled. This is almost the same trick we used in the
Reset() method, again
using the flexibility and power of
semop(). Some systems also support a timed
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.205.27