Win32 thread synchronization primitives: a quick overview
Posted: (EET/GMT+2)
I have been working with low-level programming with Windows 2000 lately, and wanted to share a quick post about thread/process synchronization. These are useful if you need to develop multithreaded applications, or need to make sure two or more processes don't race on the same resources. For native Windows (Win32) developers, Windows provides several built-in synchronization primitives for this.
Here is a quick overview of the most common ones.
Mutexes are used for mutual exclusion (this is where the name comes from). Only one thread can own a mutex at a time. Here is an example with C:
HANDLE hMutex = CreateMutex(NULL, FALSE, L"MyMutex"); WaitForSingleObject(hMutex, INFINITE); /* ...protected code section here... */ ReleaseMutex(hMutex); CloseHandle(hMutex);
Mutexes can also be named, which allows sharing between processes.
Semaphores allow a limited number of threads to access a resource at the same time.
HANDLE hSemaphore = CreateSemaphore(NULL, 3, 3, NULL); WaitForSingleObject(hSemaphore, INFINITE); /* up to 3 threads can run */ ReleaseSemaphore(hSemaphore, 1, NULL); CloseHandle(hSemaphore);
Events are useful for signaling between threads. One thread sets the event, others wait for it.
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); /* in one thread */ SetEvent(hEvent); /* in another thread */ WaitForSingleObject(hEvent, INFINITE); CloseHandle(hEvent);
The second parameter of CreateEvent controls whether the event is manual-reset or auto-reset.
Shared memory allows processes to exchange data directly. This is typically done with file mapping objects.
HANDLE hMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 1024, L"MySharedMem"); LPVOID pData = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 1024); /* read/write shared data */ UnmapViewOfFile(pData); CloseHandle(hMap);
Shared memory is usually combined with another synchronization primitive such as a mutex or event to coordinate access.
I've found that there are some things worth keeping in mind:
- Always release handles with
CloseHandle. - Use named objects when synchronization is needed across processes.
- Avoid deadlocks by keeping locked code sections small and fast.
- Combine primitives when needed, for example shared memory + mutex.
As you can see, Windows provides several low-level primitives for synchronization. Choose mutexes for exclusive access, semaphores for limited concurrency, events for signaling, and shared memory for data exchange.