A Quick and Versatile Synchronization Object

 

Dan Chou
Developer Support, Microsoft Corporation

June 1998

Introduction

Microsoft® Win32®-based operating systems such as Windows® 95, Windows NT®, and Windows CE all provide a multitasking, multithreaded environment. Whenever more than one thread tries to access the same piece of data or shared resource at the same time, you need some type of synchronization to control access to that data. Fortunately, the Win32 application programming interface (API) provides a wide range of synchronization objects that can control access to shared resources. They each have their own advantages and disadvantages in terms of speed, functionality, and the ability to synchronize across processes. They even have varying levels of support among the different Win32-based operating systems. Unfortunately, no one synchronization object exists that is fast, works across processes, provides resource counting, and works on all Win32 platforms. Until now.

This article develops a new synchronization object called a metered section. It has the advantages of speed, the ability to synchronize across processes, the ability to control access to a shared resource with more than just mutual exclusion semantics (resource counting), and supports all current Win32 platforms. When would you need the more flexible resource counting semantics instead of just providing exclusive access? What if your computer has four modems, each of which can be assigned to just one thread at a time? If you use a synchronization object that only supports exclusive access, you would only be able to synchronize access to a single shared modem. If you use a synchronization object that supports resource counting, the synchronization object would know there are four resources to be shared. It would allow four threads to each use one of the four modems at the same time and block other threads requesting access until one of the four existing threads finishes using its modem. Resource counting is a superset of exclusive access and reduces to exclusive access when there is only one resource being shared.

This article assumes a good understanding of synchronization concepts.

Summary of Synchronization Objects

First, let's take a look at some existing synchronization objects and analyze their advantages and disadvantages. Table 1 provides a summary of existing Win32 synchronization objects.

Table 1. Synchronization Objects Summary

Name Relative speed Cross process Resource counting Supported platforms
Critical Section Fast No No (Exclusive Access) 95/NT/CE
Mutex Slow Yes No (Exclusive Access) 95/NT/CE
Semaphore Slow Yes Yes 95/NT
Event Slow Yes Yes* 95/NT/CE
Metered Section Fast Yes Yes 95/NT/CE

* Events can be used for resource counting, but they do not keep track of the count for you.

Critical Section

Critical sections are one of the more primitive synchronization objects in Win32. They synchronize exclusive access to shared data between threads within a single process. As long as no contention for gaining access to a critical section exists, the critical section code runs entirely in user mode, making it extremely fast. You don't have to pay the speed penalty of transitioning between user and kernel mode. When there is contention for a critical section, however, all is not lost; it simply falls back to using a kernel event object for synchronization. Because events are kernel objects, when there is contention, the relatively expensive transition to kernel mode must be made. But since the thread will block anyway, this transition time isn't usually very significant compared to the amount of time the thread is actually blocked. Because the critical section does not have a named kernel object associated with it, one of its main disadvantages is that it cannot synchronize access between processes.

Mutex

In Win32, a mutex is a kernel object. Implementing the mutex as a kernel object solves the critical section's main disadvantage. Mutexes can synchronize between processes, but this ability comes at the sake of speed. Each time a process calls a wait function such as WaitForSingleObject on a mutex, the relatively expensive transition between user mode and kernel mode is made. When there is no contention for the shared resource, and the mutex is waited on and released numerous times, the time for this transition can add up and increase the running time of the application. Like critical sections, mutexes can only be used to synchronize exclusive access.

Semaphore

Semaphores are very similar to mutexes. Like mutexes, semaphores are implemented as kernel objects. This gives them the same advantage of working across processes, and the same disadvantage of being relatively slow. However, a mutex and a semaphore differ in that a semaphore can do more than just provide exclusive access to shared data. Semaphores can be used for resource counting. Where mutexes and critical sections allow only one thread to gain access to a shared resource at a time, semaphores allow a set number of threads to gain access to a shared resource. Additionally, Windows CE does not currently support semaphores, which makes porting applications that rely heavily on semaphores to Windows CE more difficult.

Event

Events are primitive kernel synchronization objects on which other synchronization objects can be built. By themselves they are relatively slow, but they can synchronize access between processes by using named events. Depending on how they are used, events are capable of providing resource counting, but do not keep track of the count by themselves.

Metered Section

Metered sections are basically an extension of critical sections. Metered sections build upon critical sections in two important ways: They add the ability to synchronize threads across processes, and they provide resource counting semantics similar to the semaphore kernel object (thus the "metered" part of the name). So far the metered section may sound a lot like a semaphore, but it makes both of these extensions without sacrificing speed.

The idea behind metered sections was to develop a synchronization with the speed of a critical section and the cross-process resource counting of a semaphore. A secondary goal was to make them compatible with all Win32 platforms.

How to Use Metered Sections

If you've used any of the common Win32 synchronization objects before, using metered sections shouldn't be too different. There are five functions used for working with metered sections. They purposefully look and act like the functions used for manipulating critical sections and semaphores.

Note The application programmer using metered sections should treat a pointer to a METERED_SECTION structure as an opaque pointer and not modify the contents of the structure directly. All access should be done through the following metered section interface.

CreateMeteredSection

The CreateMeteredSection function creates a named or unnamed metered section object.

The following code shows the CreateMeteredSection function prototype:

LPMETERED_SECTION CreateMeteredSection(
  LONG lInitialCount,  // Initial count
  LONG lMaximumCount,  // Maximum count
  LPCTSTR lpName       // Pointer to metered section name
);
 

Parameters

  • lInitialCount
    Specifies an initial count of open slots for the metered section. This value must be greater than or equal to zero and less than or equal to lMaximumCount. A slot is available when the count is greater than zero and none is available when it is zero. The count is decreased by one whenever the EnterMeteredSection function releases a thread that was waiting for the metered section. The count is increased by a specified amount by calling the LeaveMeteredSection function.

  • lMaximumCount
    Specifies the maximum number of available slots for the metered section. This value must be greater than zero.

  • lpName
    Pointer to a null-terminated string specifying the name of the metered section. The name is limited to MAX_METSECT_NAMELEN characters, and can contain any character except the backslash path-separator character (\). Name comparison is case sensitive.

    If lpName matches the name of an existing named metered section, the lInitialCount and lMaximumCount parameters are ignored because they have already been set by the process that originally created the metered section.

    If lpName is NULL, the metered section is created without a name.

Return values

If the function succeeds, the return value is a pointer to a metered section. If the named metered section existed before the function call, the function returns a pointer to the existing metered section and GetLastError returns ERROR_ALREADY_EXISTS.

If the function fails, the return value is NULL. To get extended error information, call GetLastError.

OpenMeteredSection

The OpenMeteredSection function returns a pointer to an existing named metered section.

Note Windows CE does not support this function.

The following code shows the OpenMeteredSection function prototype:

LPMETERED_SECTION OpenMeteredSection(
  LPCTSTR lpName    // Pointer to metered section name
);

Parameters

  • lpName
    Pointer to a null-terminated string that names the metered section to be opened. Name comparisons are case sensitive.

Return values

If the function succeeds, the return value is a pointer to a metered section. If the function fails, the return value is NULL. To get extended error information, call GetLastError.

EnterMeteredSection

The EnterMeteredSection function returns when one of the following occurs:

  • The specified metered section has an available slot.
  • The time-out interval elapses.
  • The following code shows the EnterMeteredSection function prototype:
DWORD EnterMeteredSection(
  LPMETERED_SECTION lpMetSect, // Pointer to a metered section
  DWORD dwMilliseconds         // Time-out interval in milliseconds
);
 

Parameters

  • lpMetSect
    Pointer to a metered section.
  • dwMilliseconds
    Specifies the time-out interval, in milliseconds. The function returns if the interval elapses, even if the metered section does not have an open slot. If dwMilliseconds is zero, the function tests the metered section's state and returns immediately. If dwMilliseconds is INFINITE, the function's time-out interval never elapses.

Return values

If the function succeeds, the return value is WAIT_OBJECT_0. If the function times out, the return value is WAIT_TIMEOUT. To get extended error information, call GetLastError.

Remarks

The EnterMeteredSection function checks for an available slot in the specified metered section. If the metered section does not have an available slot, the calling thread enters an efficient wait state. The thread consumes very little processor time while waiting for a slot to become free.

LeaveMeteredSection

The LeaveMeteredSection function increases the number of available slots of a metered section by a specified amount.

The following code shows the LeaveMeteredSection function prototype:

BOOL LeaveMeteredSection(
  LPMETERED_SECTION lpMetSect, // Pointer to a metered section
  LONG lReleaseCount,          // Amount to add to current count
  LPLONG lpPreviousCount       // Address of previous count
);

Parameters

  • lpMetSect
    Pointer to a metered section. The CreateMeteredSection or OpenMeteredSection function returns this pointer.
  • lReleaseCount
    Specifies the number of the metered section's slots to release. The value must be greater than zero. If the specified amount would cause the metered section's count to exceed the maximum number of available slots specified during creation of the metered section, the number of available slots is not changed and the function returns FALSE.
  • lpPreviousCount
    Pointer to a 32-bit LONG to receive the previous metered section open slot count. This parameter can be NULL if the previous count is not required.

Return values

If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended error information, call GetLastError.

CloseMeteredSection

The CloseMeteredSection function closes an open metered section pointer.

The following code shows the CloseMeteredSection function prototype:

void CloseMeteredSection(
  LPMETERED_SECTION lpMetSect // Pointer to the metered section to close
);

Parameters

  • lpMetSect
    Identifies a metered section pointer.

Return values

None

How Metered Sections Work

The following two structures represent the heart of the metered section:

typedef struct _METSECT_SHARED_INFO {
    BOOL   fInitialized;     // Is the metered section initialized?
    LONG   lSpinLock;        // Used to gain access to this structure
    LONG   lThreadsWaiting;  // Count of threads waiting
    LONG   lAvailableCount;  // Available resource count
    LONG   lMaximumCount;    // Maximum resource count
} METSECT_SHARED_INFO, *LPMETSECT_SHARED_INFO;

typedef struct _METERED_SECTION {
    HANDLE hEvent;           // Handle to a kernel event object
    HANDLE hFileMap;         // Handle to memory-mapped file
    LPMETSECT_SHARED_INFO lpSharedInfo; // Pointer to mapped view of 
                                        //  the memory mapped file
} METERED_SECTION, *LPMETERED_SECTION;

A metered section is actually an opaque pointer to a METERED_SECTION structure. The structure contains three fields. The first is a handle to an event object; the second is a handle to a memory mapped file backed by the system page file; and the third is a pointer to the mapped view of the file containing a METSECT_SHARED_INFO structure, which contains information that can potentially be shared across processes.

The shared information consists of one BOOL and four LONGs. The fInitialized flag signals that the metered section has been initialized. The lThreadsWaiting field counts the number of threads waiting to gain access to the shared resource. The lAvailableCount field indicates the number of slots (number of resources) available. The lMaximumCount field indicates the maximum number of free slots for the resource this metered section is controlling access to. The lSpinLock field synchronizes access to the METSECT_SHARED_INFO structure itself so that changes to lAvailableSlots and lThreadsWaiting can all be made atomically from the calling thread's point of view.

When you first call CreateMeteredSection to create a metered section, several things happen. First, the code allocates memory for a METERED_SECTION and fills it in. That means an automatically reset event is created and its handle is assigned to hEvent, a memory-mapped file large enough to hold a METSECT_SHARED_INFO structure is created and its handle assigned to hFileMap, and a pointer to a mapped view of that file is assigned to lpSharedInfo. If a name is passed in to CreateMeteredSection in order to create a named metered section, both the event object and memory mapped file are created as named objects. Since all of the kernel objects use the same name space, the event object and memory mapped file need to have unique names. The event object derives its name by prepending "DKC_MSECT_EVT_" to the metered section's name. The memory-mapped file similarly derives its name by prepending "DKC_MSECT_MMF_" to the metered section's name. Like other named kernel objects in Win32, names for metered sections are case sensitive.

Other processes that pass an identical name to CreateMeteredSection or OpenMeteredSection will get a pointer to a metered section that internally calls CreateEvent or OpenEvent respectively to obtain a handle to the previously created named event object. Likewise, the metered section internally calls CreateFileMapping or OpenFileMapping respectively to obtain a handle to the previously created memory mapped file. When the new metered section maps a view of the file, both the original and new metered section will have a pointer to the same shared data. By using the same event object and the same shared information between processes, a named metered section object can be used to synchronize between processes. If a metered section is created without a name, both the event object and memory mapped file are created without names. A metered section created in this manner can only synchronize access between threads of the same process. The use of a memory mapped file is not required to synchronize access between threads of the same process, but, because the only speed penalty occurs during their creation and destruction, they are used for the sake of consistency.

Each time a thread wants to enter a metered section, it calls EnterMeteredSection. Internally, this function must gain access to the shared lSpinLock field. Access to the spin lock is obtained by repeatedly setting the lSpinLock field to "1" in a call to InterlockedExchange (as shown in the GetMeteredSectionLock function code that follows). The InterlockedExchange function returns the previous value of lSpinLock. InterlockedExchange is called until the return value is "0," indicating that no one else owned the lock. Between each call to InterlockedExchange is a call to Sleep(0). If there happens to be contention in obtaining the spin lock, the call to sleep causes the calling thread to give up the remainder of its time slice instead of wasting the rest of the time slice futilely calling InterlockedExchange. Your initial reaction to this spin lock might be that spin locks waste CPU time. In this case however, the metered sections were designed so that the spin lock is held for only a few instructions so contention for the spin lock itself will be minimized, and if there is contention, the spin lock will be granted quickly.

void GetMeteredSectionLock(LPMETERED_SECTION lpMetSect)
{
    // Spin and get access to the metered section lock
    while (InterlockedExchange(&(lpMetSect->lpSharedInfo->lSpinLock), 1) != 0)
        Sleep(0);
}

Once the thread calling EnterMeteredSection has obtained the spin lock, it can modify the contents of the METSECT_SHARED_INFO structure without worrying about other threads modifying the contents of the structure at the same time (see the excerpt from EnterMeteredSection code that follows). It checks the lAvailableCount field, and if there is a free slot available it decrements the available count, releases the spin lock, and gains access to the shared resource. If there are no available slots, there is contention for the shared resource. In this case, the calling thread must block regardless, so it falls back and uses the kernel event object for synchronization. The event object is reset, the spin lock is released, and the calling thread waits on the event object until it is either signaled or times out.

GetMeteredSectionLock(lpMetSect);  // Get the spin lock
// We have access to the metered section, everything we do now will be atomic
if (lpMetSect->lpSharedInfo->lAvailableCount >= 1)
{
    lpMetSect->lpSharedInfo->lAvailableCount--;
    ReleaseMeteredSectionLock(lpMetSect);
    return WAIT_OBJECT_0; // Grant access to the shared resource
}

// Couldn't get in. Wait on the event object
lpMetSect->lpSharedInfo->lThreadsWaiting++;
ResetEvent(lpMetSect->hEvent);
ReleaseMeteredSectionLock(lpMetSect); // Release the spin lock
if (WaitForSingleObject(lpMetSect->hEvent, 
      dwMilliseconds) == WAIT_TIMEOUT)
{
    return WAIT_TIMEOUT;
}

When a thread that has entered the metered section is ready to release its shared resources, it calls LeaveMeteredSection with the number of shared resources to give up. An observant reader might notice that it is possible to release more than one resource with a single call to LeaveMeteredSection, but each call to EnterMeteredSection can only obtain access to a single resource at a time. This was done to follow the conventions used for semaphores. Because LeaveMeteredSection modifies the contents of the METSECT_SHARED_INFO structure, it needs to gain access to the spin lock. Once the spin lock is obtained, the lAvailableCount field is incremented by the number of resources given up. Since there is now at least one or more shared resources available, lThreadsWaiting is checked to see if any threads are waiting. If there are no threads waiting, the spin lock is released and the function returns. If threads are waiting, the event object (hEvent) is set the appropriate number of times with SetEvent so that as many waiting threads are woken up as there are resources available. The following code fragment shows how this is done. Because we are using automatically reset events, each call to SetEvent wakes up one thread. If manually reset events were used, each call to SetEvent would wake up all of the waiting threads, and they would all contend for the metered section. If many more threads were waiting for resources than there were resources available, waking up all of the threads would be wasteful. Several threads would burn CPU cycles trying to gain access to the resource only to have to wait on the event object again.

// Set the event the appropriate number of times
lReleaseCount = min(lReleaseCount, 
      lpMetSect->lpSharedInfo->lThreadsWaiting);
if (lpMetSect->lpSharedInfo->lThreadsWaiting)
{
    for (iCount=0; iCount < lReleaseCount ; iCount++)
    {
        lpMetSect->lpSharedInfo->lThreadsWaiting--;
        SetEvent(lpMetSect->hEvent);
    }
}

The application calls CloseMeteredSection when it no longer needs the metered section. This call simply cleans up the metered section by unmapping the view of the METSECT_SHARED_INFO structure, closing the handles to the event object and memory mapped file, and freeing the memory allocated for the METERED_SECTION structure.

Another important point I should mention about metered sections is that all of the APIs used in their implementation are supported on all Win32 platforms, with the exception of OpenEvent and OpenFileMapping, which are not supported on Windows CE. Only the OpenMeteredSection function uses these APIs, so, through conditional compilation (using #ifdef), the code is conveniently left out when compiled for Windows CE. The loss of this API on Windows CE is not as bad as it may sound. OpenEvent and OpenFileMapping are actually redundant functions. CreateEvent and CreateFileMapping can both create their respective kernel objects as well as open them if given a name that refers to a preexisting object. The side effect is that CreateMeteredSection can actually be used to open a preexisting metered section as well. Metered sections are even more important on Windows CE than other Win32 platforms because Windows CE does not support semaphores. Metered sections are the only synchronization objects on Windows CE that fully supports resource counting.

Conclusion

Win32 provides a number of synchronization objects with a variety of functionality. This article puts yet one more synchronization object at your disposal. If your application requires a fast synchronization object that works on all current Win32 platforms and is capable of resource counting between both threads and processes, metered sections may be just what you're looking for.

Appendix

Below is the complete source code for MeteredSection.h and MeteredSection.c. This is the same code that you can copy from the link at the top of this article.

/************************************************************
    Module Name: MeteredSection.h
    Author: Dan Chou
    Description: Defines the metered section synchronization object
************************************************************/

#ifndef _METERED_SECTION_H_
#define _METERED_SECTION_H_

#define MAX_METSECT_NAMELEN 128

#ifdef __cplusplus
extern "C"
{
#endif // __cplusplus

// Shared info needed for metered section
typedef struct _METSECT_SHARED_INFO {
    BOOL   fInitialized;     // Is the metered section initialized?
    LONG   lSpinLock;        // Used to gain access to this structure
    LONG   lThreadsWaiting;  // Count of threads waiting
    LONG   lAvailableCount;  // Available resource count
    LONG   lMaximumCount;    // Maximum resource count
} METSECT_SHARED_INFO, *LPMETSECT_SHARED_INFO;

// The opaque Metered Section data structure
typedef struct _METERED_SECTION {
    HANDLE hEvent;           // Handle to a kernel event object
    HANDLE hFileMap;         // Handle to memory mapped file
    LPMETSECT_SHARED_INFO lpSharedInfo;
} METERED_SECTION, *LPMETERED_SECTION;

// Interface functions
LPMETERED_SECTION
CreateMeteredSection(LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName);

#ifndef _WIN32_WCE
LPMETERED_SECTION OpenMeteredSection(LPCTSTR lpName);
#endif

DWORD EnterMeteredSection(LPMETERED_SECTION lpMetSect, 
      DWORD dwMilliseconds);
BOOL LeaveMeteredSection(LPMETERED_SECTION lpMetSect, 
      LONG lReleaseCount, LPLONG lpPreviousCount);
void CloseMeteredSection(LPMETERED_SECTION lpMetSect);

#ifdef __cplusplus
}
#endif // __cplusplus

#endif // _METERED_SECTION_H_

/************************************************************
    Module Name: MeteredSection.c
    Author: Dan Chou
    Description: Implements the metered section synchronization object
************************************************************/

#include <windows.h>
#include <tchar.h>
#include "MeteredSection.h"

// Internal function declarations
BOOL InitMeteredSection(LPMETERED_SECTION lpMetSect, LONG lInitialCount, 
      LONG lMaximumCount, LPCTSTR lpName, BOOL bOpenOnly);
BOOL CreateMetSectEvent(LPMETERED_SECTION lpMetSect, LPCTSTR lpName, BOOL 
      bOpenOnly);
BOOL CreateMetSectFileView(LPMETERED_SECTION lpMetSect, LONG lInitialCount, 
      LONG lMaximumCount, LPCTSTR lpName, BOOL bOpenOnly);
void GetMeteredSectionLock(LPMETERED_SECTION lpMetSect);
void ReleaseMeteredSectionLock(LPMETERED_SECTION lpMetSect);

/*
 * CreateMeteredSection
 */
LPMETERED_SECTION CreateMeteredSection(LONG lInitialCount, 
      LONG lMaximumCount, LPCTSTR lpName)
{
    LPMETERED_SECTION lpMetSect;

    // Verify the parameters
    if ((lMaximumCount < 1)             ||
        (lInitialCount > lMaximumCount) ||
        (lInitialCount < 0)             ||
        ((lpName) && (_tcslen(lpName) > MAX_METSECT_NAMELEN)))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return NULL;
    }

    // Allocate memory for the metered section
    lpMetSect = (LPMETERED_SECTION)malloc(sizeof(METERED_SECTION));

    // If the memory for the metered section was allocated okay, 
    initialize it
    if (lpMetSect)
    {
        if (!InitMeteredSection(lpMetSect, lInitialCount, 
      lMaximumCount, lpName, FALSE))
        {
            CloseMeteredSection(lpMetSect);
            lpMetSect = NULL;
        }
    }
    return lpMetSect;
}

/*
 * OpenMeteredSection
 */
#ifndef _WIN32_WCE
LPMETERED_SECTION OpenMeteredSection(LPCTSTR lpName)
{
    LPMETERED_SECTION lpMetSect = NULL;

    if (lpName)
    {
        lpMetSect = (LPMETERED_SECTION)malloc(sizeof(METERED_SECTION));

        // If the memory for the metered section was allocated okay
        if (lpMetSect)
        {
            if (!InitMeteredSection(lpMetSect, 0, 0, lpName, TRUE))
            {
                // Metered section failed to initialize
                CloseMeteredSection(lpMetSect);
                lpMetSect = NULL;
            }
        }
    }
    return lpMetSect;
}
#endif

/*
 * EnterMeteredSection
 */
DWORD EnterMeteredSection(LPMETERED_SECTION lpMetSect, 
      DWORD dwMilliseconds)
{
    while (TRUE)
    {
        GetMeteredSectionLock(lpMetSect);

        // We have access to the metered section, everything we 
      do now will be atomic
        if (lpMetSect->lpSharedInfo->lAvailableCount >= 1)
        {
            lpMetSect->lpSharedInfo->lAvailableCount--;
            ReleaseMeteredSectionLock(lpMetSect);
            return WAIT_OBJECT_0;
        }

        // Couldn't get in. Wait on the event object
        lpMetSect->lpSharedInfo->lThreadsWaiting++;
        ResetEvent(lpMetSect->hEvent);
        ReleaseMeteredSectionLock(lpMetSect);
        if (WaitForSingleObject(lpMetSect->hEvent, 
         dwMilliseconds) == WAIT_TIMEOUT)
        {
            return WAIT_TIMEOUT;
        }
    }
}

/*
 * LeaveMeteredSection
 */
BOOL LeaveMeteredSection(LPMETERED_SECTION lpMetSect, LONG lReleaseCount, 
         LPLONG lpPreviousCount)
{
    int iCount;
    GetMeteredSectionLock(lpMetSect);

    // Save the old value if they want it
    if (lpPreviousCount)
    {
        *lpPreviousCount = lpMetSect->lpSharedInfo->lAvailableCount;
    }

    // We have access to the metered section, 
    everything we do now will be atomic
    if ((lReleaseCount < 0) ||
        (lpMetSect->lpSharedInfo->lAvailableCount+lReleaseCount >
         lpMetSect->lpSharedInfo->lMaximumCount))
    {
        ReleaseMeteredSectionLock(lpMetSect);
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }
    lpMetSect->lpSharedInfo->lAvailableCount += lReleaseCount;
    
    // Set the event the appropriate number of times
    lReleaseCount = 
      min(lReleaseCount,lpMetSect->lpSharedInfo->lThreadsWaiting);
    if (lpMetSect->lpSharedInfo->lThreadsWaiting)
    {
        for (iCount=0; iCount < lReleaseCount ; iCount++)
        {
            lpMetSect->lpSharedInfo->lThreadsWaiting--;
            SetEvent(lpMetSect->hEvent);
        }
    }
    ReleaseMeteredSectionLock(lpMetSect);
    return TRUE;
}

/*
 * CloseMeteredSection
 */
void CloseMeteredSection(LPMETERED_SECTION lpMetSect)
{
    if (lpMetSect)
    {
        // Clean up
        if (lpMetSect->lpSharedInfo) 
         UnmapViewOfFile(lpMetSect->lpSharedInfo);
        if (lpMetSect->hFileMap) CloseHandle(lpMetSect->hFileMap);
        if (lpMetSect->hEvent) CloseHandle(lpMetSect->hEvent);
        free(lpMetSect);
    }
}

/*
 * InitMeteredSection
 */
BOOL InitMeteredSection(LPMETERED_SECTION lpMetSect, 
         LONG lInitialCount, LONG lMaximumCount,
                        LPCTSTR lpName, BOOL bOpenOnly)
{
    // Try to create the event object
    if (CreateMetSectEvent(lpMetSect, lpName, bOpenOnly))
    {
        // Try to create the memory mapped file
        if (CreateMetSectFileView(lpMetSect, 
         lInitialCount, lMaximumCount, lpName, bOpenOnly))
        {
            return TRUE;
        }
    }

    // Error occured, return FALSE so the caller knows to clean up
    return FALSE;
}

/*
 * CreateMetSectEvent
 */
BOOL CreateMetSectEvent(LPMETERED_SECTION lpMetSect, 
         LPCTSTR lpName, BOOL bOpenOnly)
{
    TCHAR sz[MAX_PATH];
    if (lpName)
    {
        wsprintf(sz, _TEXT("DKC_MSECT_EVT_%s"), lpName);

#ifndef _WIN32_WCE
        if (bOpenOnly)
        {
            lpMetSect->hEvent = OpenEvent(0, FALSE, sz);
        }
        else
        {
#endif
            // Create an auto-reset named event object
            lpMetSect->hEvent = CreateEvent(NULL, FALSE, FALSE, sz);
#ifndef _WIN32_WCE
        }
#endif
    }
    else
    {
        // Create an auto-reset unnamed event object
        lpMetSect->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    }
    return (lpMetSect->hEvent ? TRUE : FALSE);
}

/*
 * CreateMetSectFileView
 */
BOOL CreateMetSectFileView(LPMETERED_SECTION lpMetSect, 
         LONG lInitialCount, LONG lMaximumCount,
                           LPCTSTR lpName, BOOL bOpenOnly)
{
    TCHAR sz[MAX_PATH];
    DWORD dwLastError; 

    if (lpName)
    {
        wsprintf(sz, _TEXT("DKC_MSECT_MMF_%s"), lpName);

#ifndef _WIN32_WCE
        if (bOpenOnly)
        {
            lpMetSect->hFileMap = OpenFileMapping(0, FALSE, sz);
        }
        else
        {
#endif
            // Create a named file mapping
            lpMetSect->hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, 
         NULL, PAGE_READWRITE, 0, sizeof(METSECT_SHARED_INFO), sz);
#ifndef _WIN32_WCE
        }
#endif
    }
    else
    {
        // Create an unnamed file mapping
        lpMetSect->hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, 
         NULL, PAGE_READWRITE, 0, sizeof(METSECT_SHARED_INFO), NULL);
    }
 
    // Map a view of the file
    if (lpMetSect->hFileMap)
    {
        dwLastError = GetLastError();
        lpMetSect->lpSharedInfo = (LPMETSECT_SHARED_INFO) 
         MapViewOfFile(lpMetSect->hFileMap, FILE_MAP_WRITE, 0, 0, 0);
        if (lpMetSect->lpSharedInfo)
        {
            if (dwLastError != ERROR_ALREADY_EXISTS)
            {
                lpMetSect->lpSharedInfo->lSpinLock = 0;
                lpMetSect->lpSharedInfo->lThreadsWaiting = 0;
                lpMetSect->lpSharedInfo->lAvailableCount = lInitialCount;
                lpMetSect->lpSharedInfo->lMaximumCount = lMaximumCount;
                InterlockedExchange(&(lpMetSect->lpSharedInfo-
            >fInitialized), TRUE);
            }
            else
            {   // Already exists; wait for it to be initialized by the creator
              while (!lpMetSect->lpSharedInfo->fInitialized) Sleep(0);
            }
            return TRUE;
        }
    }
    return FALSE;
}

/*
 * GetMeteredSectionLock
 */
void GetMeteredSectionLock(LPMETERED_SECTION lpMetSect)
{
    // Spin and get access to the metered section lock
    while (InterlockedExchange(&(lpMetSect->lpSharedInfo->lSpinLock), 1) != 0)
        Sleep(0);
}

/*
 * ReleaseMeteredSectionLock
 */
void ReleaseMeteredSectionLock(LPMETERED_SECTION lpMetSect)
{
    InterlockedExchange(&(lpMetSect->lpSharedInfo->lSpinLock), 0);
}