Interlocked Variable Access
Applications must synchronize access to variables that are shared by multiple threads. Applications must also ensure that operations on these variables are performed atomically (performed in their entirety or not at all.) Simple reads and writes to properly-aligned 32-bit variables are atomic operations. In other words, you will not end up with only one portion of the variable updated; all bits are updated in an atomic fashion. However, access is not guaranteed to be synchronized. If two threads are reading and writing from the same variable, you cannot determine if one thread will perform its read operation before the other performs its write operation. Simple reads and writes to properly aligned 64-bit variables are atomic on 64-bit Windows. Reads and writes to 64-bit values are not guaranteed to be atomic on 32-bit Windows. Reads and writes to variables of other sizes are not guaranteed to be atomic on any platform.
The Interlocked API
The interlocked functions provide a simple mechanism for synchronizing access to a variable that is shared by multiple threads. They also perform operations on variables in an atomic manner. The threads of different processes can use these functions if the variable is in shared memory. The InterlockedIncrement() and InterlockedDecrement() functions combine the steps involved in incrementing or decrementing a variable into an atomic operation. This feature is useful in a multitasking operating system, in which the system can interrupt one thread's execution to grant a slice of processor time to another thread. Without such synchronization, two threads could read the same value, increment it by 1, and store the new value for a total increase of 1 instead of 2. The interlocked variable-access functions protect against this kind of error. The InterlockedExchange() and InterlockedExchangePointer() functions atomically exchange the values of the specified variables. The InterlockedExchangeAdd() function combines two operations: adding two variables together and storing the result in one of the variables. The InterlockedCompareExchange(), InterlockedCompare64Exchange128(), and InterlockedCompareExchangePointer() functions combine two operations: comparing two values and storing a third value in one of the variables, based on the outcome of the comparison.
The InterlockedAnd(), InterlockedOr(), and InterlockedXor() functions atomically perform AND, OR, and XOR operations, respectively. There are functions that are specifically designed to perform interlocked variable access on 64-bit memory values and addresses, and are optimized for use on 64-bit Windows. Each of these functions contains 64 in the name; for example, InterlockedDecrement64() and InterlockedCompareExchangeAcquire64(). Most of the interlocked functions provide full memory barriers on all Windows platforms. There are also functions that combine the basic interlocked variable access operations with the acquire and release memory access semantics supported by certain processors. Each of these functions contains the word Acquire or Release in their names; for example, InterlockedDecrementAcquire() and InterlockedDecrementRelease(). Acquire memory semantics specify that the memory operation being performed by the current thread will be visible before any other memory operations are attempted. Release memory semantics specify that the memory operation being performed by the current thread will be visible after all other memory operations have been completed. These semantics allow you to force a memory operations to be performed in a specific order. You should use acquire semantics when entering a protected region and release semantics when leaving it.
Interlocked Singly Linked Lists
An interlocked singly linked list (SList) eases the task of insertion and deletion from a linked list. SLists are implemented using a nonblocking algorithm to provide atomic synchronization, increase system performance, and avoid problems such as priority inversion and lock convoys. SLists are straightforward to implement and use in 32-bit code. However, it is challenging to implement them in 64-bit code because the amount of data exchangeable by the native interlocked exchange primitives is not double the address size, as it is in 32-bit code. Therefore, SLists enable porting high-end scalable algorithms to Windows. Applications can use SLists by calling the InitializeSListHead() function to initialize the head of the list. To insert items into the list, use the InterlockedPushEntrySList() function. To delete items from the list, use the InterlockedPopEntrySList() function. The following table lists the SList functions.
Function |
Description |
InitializeSListHead() |
Initializes the head of a singly linked list. |
InterlockedFlushSList() |
Flushes the entire list of items in a singly linked list. |
InterlockedPopEntrySList() |
Removes an item from the front of a singly linked list. |
InterlockedPushEntrySList() |
Inserts an item at the front of a singly linked list. |
QueryDepthSList() |
Retrieves the number of entries in the specified singly linked list. |
Timer Queues
The CreateTimerQueue() function creates a queue for timers. Timers in this queue, known as timer-queue timers, are lightweight objects that enable you to specify a callback function to be called when the specified due time arrives. The wait operation is performed by a thread in the thread pool. To add a timer to the queue, call the CreateTimerQueueTimer() function. To update a timer-queue timer, call the ChangeTimerQueueTimer() function. You can specify a callback function to be executed by a worker thread from the thread pool when the timer expires. To cancel a pending timer, call the DeleteTimerQueueTimer() function. When you are finished with the queue of timers, call the DeleteTimerQueueEx() function to delete the timer queue. Any pending timers in the queue are canceled and deleted.