Holy cow, I wrote a book!
You never need to call the HeapLock and HeapUnlock functions under normal operation. Assuming the heap is serialized (which is the default), all the standard heap functions like HeapAllocate and HeapFree will automatically serialize.
HeapLock
HeapUnlock
HeapAllocate
HeapFree
In fact, the way they serialize is by calling the¹ HeapLock and HeapUnlock functions!
Nearly all heap operations complete in a single call. If your heap is serialized, this means that the heap operation takes the heap lock, does its work, and then releases the heap lock and returns. If all heap operations were like this, then there would be no need for HeapLock or HeapUnlock.
Unfortunately, there is also the HeapWalk function, which does a little bit of work, and then returns with a partial result. The design for HeapWalk is that the application calls the function repeatedly until it either gets all the results it wants, or gets bored and gives up. But wait, what if the heap changes while the application is trying to walk through it? To prevent that from happening, the program can call HeapLock before starting the enumeration, and HeapUnlock when it is done. During the time the heap is locked, other threads which attempt to call a HeapXxx function with that same heap will block until the heap is unlocked.
HeapWalk
HeapXxx
The ability to lock the heap creates a lot of potential for craziness, because the heap is a high-traffic area. As a result, it is very important that any code which calls HeapLock do very little while the lock is held. Take the lock, do your thing, and get out quickly.
But wait, there's more. Holding the heap lock blocks all other threads from allocating or freeing memory. This puts the heap lock very low in your lock hierarchy. Therefore, while you hold the heap lock, you cannot block on synchronization objects whose owners might try to access the heap you just locked. Consider the following:
// Code in italics is wrong. void BadIdea() { HeapLock(GetProcessHeap()); SendMessage(...); HeapUnlock(GetProcessHeap()); }
Sending a message is a big deal. The thread that is the target of the message may be waiting for the heap lock, and now you've created a deadlock. You won't proceed until that thread processes the message, but that thread can't process the message until you unlock the heap.
You might accidentally do something wrong while hold the heap lock if you happen to trigger a delay-loaded DLL, in which case your call into that other DLL turns into a call to LoadLibrary, and now you've lost control. In practice, the only thing you should be doing while holding the heap lock is calling HeapWalk and saving the results locally, and in a way that doesn't allocate or free memory on the heap you are walking! Wait until after you unlock the heap to start studying the results you collected or transfer the raw data into a more suitable data structure.
LoadLibrary
Bonus chatter
Note that if you call HeapLock or HeapUnlock on a heap that was created without serialization (HEAP_NO_SERIALIZATION), then the results are undefined. That's because passing the HEAP_NO_SERIALIZATION flag means "Hey, Heap Manager, don't bother locking this heap. I will take responsibility for ensuring that only one thread operates on this heap at a time." If you later call HeapLock on a no-serialization heap, the heap manager will say, "Wha? You said that you would take care of serialization, not me!"
HEAP_NO_SERIALIZATION
It's like ordering a car and saying, "Don't bother installing door locks. I will take responsibility for ensuring the safety of the car. (Say, by never letting the car leave a secured facility.)" And then a month later, calling OnStar and saying, "Hi, can you remotely lock my car for me? Thanks." Dude, you explicitly opted out of door locks.
(Amazingly, I encountered one developer who thought that calling HeapLock on a no-serialization heap would cause other heap operations on the heap to be blocked, even if they passed the HEAP_NO_SERIALIZATION flag to those operations. Um, no, the HeapLock function cannot lock a no-serialization heap because a no-serialization heap doesn't have lock in the first place, at your request.)
Nitpicker's corner
¹ s/the/the functional equivalents of/