Are there alternatives to _lock and _unlock in Visual Studio 2015?


A customer was using the _lock and _unlock functions in the C runtime library to take internal locks in order to avoid deadlocking with a thread they were suspending. They included this demonstration program:

#include <windows.h>
#include <process.h>
#include <stdio.h>

unsigned int __stdcall ThreadFunc(void *);

#define _HEAP_LOCK 4    /* lock for heap allocator routines */

extern "C" void _lock(int);
extern "C" void _unlock(int);

int __cdecl main(int, char**)
{
  HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0,
      ThreadFunc, NULL, 0, NULL);

  for (;;) {
    printf(".");
    _lock(_HEAP_LOCK);
    SuspendThread(hThread);
    _unlock(_HEAP_LOCK); 
    void *p = malloc(8);
    free(p);
    ResumeThread(hThread);
  }
  return 0;
}

unsigned int __stdcall ThreadFunc(void *)
{
  for (;;) {
    void *p = malloc(8);
    free(p);
  }
}

This sample program starts a worker thread that continuously allocates and frees memory from the heap. The main thread is in a loop that suspends the worker thread, and then tries to allocate memory from the heap while the worker is suspended.

Normally, this would be a problem if the worker thread happens to be in the middle of a heap operation, then the main thread will deadlock because it wants the heap lock, but the heap lock is owned by the worker thread, which is suspended.

The program addresses the problem by explicitly taking the heap lock before suspending the thread. That way, we are sure that the thread does not hold the heap lock before we suspend it. The definition of the magic number that represents the heap lock comes from the internal mtdll.h header file that comes with the C runtime source code.

The customer found that the version of the Visual C++ compiler that comes with Visual Studio 2015 no longer has the _lock and _unlock functions. As a result, their program doesn't compile any more. How can they suspend the thread without deadlocking on the heap?

The customer liaison pointed the customer to this article and suggested to the customer that they use other synchronization mechanisms instead of Suspend­Thread. The customer responded that they are developing a simulator for their product, and they need to suspend a thread as accurately as possible, so they need to use the Suspend­Thread function.

What they're doing now is not going to work well long-term, because it's taking dependencies upon the internals of the C runtime library. The C runtime library team explained,

There is no replacement for _lock and _unlock. The Universal CRT does not expose its internal locks as older CRTs did. Note that their current approach of acquiring the CRTs heap lock before suspending the thread is not sufficient to avoid deadlock. The CRT malloc calls the Windows Heap­Alloc, and the Windows heap has its own locks that it uses for synchronization.

On top of this, the documentation for Suspend­Thread cautions directly against this usage (emphasis mine):

This function is primarily designed for use by debuggers. It is not intended to be used for thread synchronization. Calling Suspend­Thread on a thread that owns a synchronization object, such as a mutex or critical section, can lead to a deadlock if the calling thread tries to obtain a synchronization object owned by a suspended thread. To avoid this situation, a thread within an application that is not a debugger should signal the other thread to suspend itself. The target thread must be designed to watch for this signal and respond appropriately.

(Further discussion here.)

We didn't understand what the customer meant by "they are developing a simulator for their product" and how that required them to suspend a thread "as accurately as possible." We asked for clarification in the hopes that understanding their scenario would help us come up with a solution, but we never did get a clarification. That didn't stop us from trying to help anyway:

If the customer is really insistent on suspending the thread in order to do inspection of the process state, there are a few options.

One option, as noted in MSDN, is to coordinate with the thread so it is suspended at only well-defined points where it does not own any locks or resource. This is what the CLR does.

Another option is to make sure that while any thread is suspended, you never take any locks or more generally try to acquire any resources. Among other things, this means preallocating memory before suspending the thread. It also means that you cannot call into external functions because you have no idea what locks those external functions may take. Suspend the thread, memcpy the results into preallocated memory, resume the thread, and then process the results. Do not call the heap or anything else¹ that may require a lock.²

The best option is to do the suspension and inspection from another process. Even in that case, you need to be careful if the inspecting process requires locks that may be owned by the process being inspected, such as shared mutexes.

Suspending a thread at a random point in its execution, and then trying to do anything interesting from within the same process is a bad idea and has high deadlock potential.

The customer liaison thanked us for the information and explained that the customer wants to suspend the thread at arbitrary points in its execution because it is an application requirement.³ They will take our recommendations into consideration while they decide what to do next.

¹ Furthermore, you would be best served to take the heap lock (Heap­Lock) before suspending the thread, because the Detours library will allocate memory during thread suspension.

² It may be difficult to avoid allocating memory. You can at least avoid the heap lock by using Virtual­Alloc instead of Heap­Alloc.

³ This sounds like a circular argument, but hey.

Comments (8)
  1. kantos says:

    To make this more FUN _lock and _unlock are removed as of VS2015. VS2017 does implement threads.h so there is a platform independent way to synchronize in C on windows via mtx_lock. That said this seems like an X/Y problem overall, so all of that is irrelevant.

  2. You do what you can, and then you let your children go to bump their heads in their own ways, unfortunately.

    HeapLock sounds like a fun constant, now it’s time to find all the ways I can insert it in old code to give maintainers a heart attack. :D

  3. Koro says:

    “Even in that case, you need to be careful if the inspecting process requires locks that may be owned by the process being inspected, such as shared mutexes.”

    This reminds me how debugging any process using WININET.DLL was a nightmare because it would deadlock the debugger (which tried to download symbols using the same DLL).

  4. Kevin says:

    > because it is an application requirement.³

    > ³ This sounds like a circular argument, but hey.

    Ha.

    In context, it sounds like what they meant to say was “Under the current design, we use the thread suspension to implement feature X, which is an application requirement. Changing our design to avoid thread suspension, while still providing feature X, costs more than our budget for this bug (and may be wholly infeasible without a complete rewrite).”

  5. Yuhong Bao says:

    FYI, I actually checked and in the release version of the universal CRT, they just use HeapAlloc anyway and only resort to alternatives if that fails.

  6. voo says:

    And another story in the unending annals of people who have no clue about concurrent programming trying to do so anyhow..

    The state of this industry is depressing sometimes.

    1. xcomcmdr says:

      That’s what happens when you say “everyone can code.”. No, not everyone can. And even then, mastering things such as concurrent programming is not an easy task.

      As a result, badly written software (or languages, like *cough* PHP *cough* ) which should never have been written are set as standards, making people miserable.

      1. Deltics says:

        … which is when someone comes along and says “we can make this hard stuff easy for everyone. Just use this framework/library/toolkit/whatever. No understanding required.”

Comments are closed.

Skip to main content