Synchronization Using Semaphores Program Example
A semaphore object is a synchronization object that maintains a count between zero and a specified maximum value. The count is decremented each time a thread completes a wait for the semaphore object and incremented each time a thread releases the semaphore. When the count reaches zero, no more threads can successfully wait for the semaphore object state to become signaled. The state of a semaphore is set to signaled when its count is greater than zero, and non-signaled when its count is zero. The semaphore object is useful in controlling a shared resource that can support a limited number of users. In the following examples, two threads are created that use a shared memory buffer. Access to the shared memory is synchronized using a semaphore. The primary thread (main) creates a semaphore object and uses this object to handshake with the secondary thread (thread_function()). The primary thread instantiates the semaphore in a state that prevents the secondary thread from acquiring the semaphore while it is initiated. After the user types in text at the console and presses ENTER, the primary thread relinquishes the semaphore. The secondary thread then acquires the semaphore and processes the shared memory area. At this point, the main thread is blocked waiting for the semaphore and will not resume until the secondary thread has relinquished control by calling ReleaseSemaphore.
In UNIX, the semaphore object functions of sem_post and sem_wait are all that are required to perform handshaking. With Windows, you must use a combination of WaitForSingleObject() and ReleaseSemaphore() in both the primary and the secondary threads in order to facilitate handshaking. The two solutions are also very different from a syntactic standpoint. The primary difference between their implementations is with the API calls that are used to manage the semaphore objects. One aspect of CreateSemaphore() that you need to be aware of is the last argument in its parameter list. This is a string parameter specifying the name of the semaphore. You should not pass a NULL for this parameter. All the kernel objects, including semaphores, are named. All kernel object names are stored in a common namespace except if it is a server running Microsoft Terminal Server, in which case there will also be a namespace for each session. If the namespace is global, one or more unassociated applications could attempt to use the same name for a semaphore. To avoid namespace contention, applications should use some unique naming convention. One solution would be to base your semaphore names on globally unique identifiers (GUIDs).
Terminal Server and Naming Semaphore Objects
As mentioned earlier, Terminal Server has multiple namespaces for kernel objects. There is one global namespace, which is used by kernel objects that are accessible by any and all client sessions and is usually populated by services. Additionally, each client session has its own namespace to prevent namespace collisions between multiple instances of the same application running in different sessions. In addition to the session and global namespaces, Terminal Server also has a local namespace. By default, the named kernel objects of an application reside in the session namespace. It is possible, however, to override what namespace will be used. This is accomplished by prefixing the name with Global\ or Local\. These prefix names are reserved by Microsoft, are case-sensitive, and are ignored if the computer is not operating as a Terminal Server.
Create a new empty Win32 console application project. Give a suitable project name and change the project location if needed.
Then, add the source file and give it a suitable name.
Next, add the following source code.
#include <Windows.h>
#include <stdio.h>
#define SHARED_SIZE 1024
// Shared global variables
WCHAR shared_area[SHARED_SIZE];
LPCTSTR lpszSemaphore = LNamedSemaphoreGedik;
HANDLE sem_t;
DWORD WINAPI thread_function(LPVOID arg)
{
LONG dwSemCount, dwWFSO1, dwWFSO2;
HANDLE hSemaphore = OpenSemaphore( SYNCHRONIZE |SEMAPHORE_MODIFY_STATE, FALSE, lpszSemaphore );
dwWFSO1 = WaitForSingleObject(hSemaphore, INFINITE);
// Value 0 means object has been signaled...
wprintf(LWaitForSingleObject() 1 returned value is 0X%.8X\n, dwWFSO1);
while(wcsncmp(Ldone, shared_area, 4) != 0)
{
wprintf(LYou input %d characters\n, wcslen(shared_area) -1);
// Increases the count of the specified semaphore object by a specified amount.
// If the function succeeds, the return value is nonzero.
// If the function fails, the return value is zero.
if(ReleaseSemaphore(hSemaphore, 1, &dwSemCount) != 0)
wprintf(LReleaseSemaphore() - hSemaphore handle has been released...\n);
else
wprintf(LFailed to release hSemaphore handle, error %u\n, GetLastError());
dwWFSO2 = WaitForSingleObject(hSemaphore, INFINITE);
wprintf(LWaitForSingleObject() 2 returned value is 0X%.8X\n, dwWFSO2);
}
if(ReleaseSemaphore(hSemaphore, 1, &dwSemCount) != 0)
wprintf(LReleaseSemaphore() - hSemaphore handle has been released...\n);
else
wprintf(LFailed to release hSemaphore handle, error %u\n, GetLastError());
if(CloseHandle(hSemaphore) != 0)
wprintf(LhSemaphore handle was closed successfully!\n);
else
wprintf(LFailed to close hSemaphore handle, error %u\n, GetLastError());
return 0;
}
int wmain()
{
HANDLE a_thread;
DWORD a_threadId, dwMWFSO1, dwMWFSO2;
DWORD thread_result;
LONG dwSemCount;
// Some info
wprintf(LThe main() process ID is %u\n, GetCurrentProcessId());
wprintf(LThe main() thread ID is %u\n, GetCurrentThreadId());
wprintf(L\n);
// Creates or opens a named semaphore object.
sem_t = CreateSemaphore( NULL, 0, 1, lpszSemaphore );
if (sem_t == NULL)
{
wprintf(LCreateSemaphore() - Semaphore initialization failed, error %u\n, GetLastError());
exit(EXIT_FAILURE);
}
else
wprintf(LCreateSemaphore() is OK!\n);
// Create a new thread.
a_thread = CreateThread(NULL, 0, thread_function, (LPVOID)NULL, 0,&a_threadId);
if (a_thread == NULL)
{
wprintf(LCreateThread() - Thread creation failed, error %u\n, GetLastError());
exit(EXIT_FAILURE);
}
else
wprintf(LCreateThread() is OK! Thread ID is %u\n, a_threadId);
wprintf(L\nInput some text. Enter 'done' to finish\n\n);
while(wcsncmp(Ldone, shared_area, 4) != 0)
{
fgetws(shared_area, SHARED_SIZE, stdin);
if(ReleaseSemaphore(sem_t, 1, &dwSemCount) != 0)
wprintf(LReleaseSemaphore() - sem_t handle has been released...\n);
else
wprintf(LFailed to release sem_t handle, error %u\n, GetLastError());
dwMWFSO1 = WaitForSingleObject(sem_t, INFINITE);
wprintf(LWaitForSingleObject() 3 returned value is 0X%.8X\n, dwMWFSO1);
wprintf(L\n);
}
wprintf(L\nWaiting for thread to finish...\n);
dwMWFSO2 = WaitForSingleObject(a_thread, INFINITE);
wprintf(LWaitForSingleObject() 4 returned value is 0X%.8X\n, dwMWFSO2);
if (dwMWFSO2 != WAIT_OBJECT_0)
{
wprintf(LWaitForSingleObject() 4 - Thread join failed, error %u, GetLastError());
exit(EXIT_FAILURE);
}
else
wprintf(LWaitForSingleObject() 4 should be OK!\n);
// Retrieve the code returned by the thread.
if(GetExitCodeThread(a_thread, &thread_result) != 0)
wprintf(LGetExitCodeThread() is OK! Thread joined, exit code %u\n, thread_result);
else
wprintf(LGetExitCodeThread() failed, error %u\n, GetLastError());
if(CloseHandle(a_thread) != 0)
wprintf(La_thread handle was closed successfully!\n);
else
wprintf(LFailed to close a_thread handle, error %u\n, GetLastError());
return 0;
}
Build and run the project. The following screenshot is a sample output.