My, those threads start up really fast nowadays

Raymond Chen

Raymond

Here’s a little puzzle
inspired by an actual bug:

// global variable
DWORD g_WorkerThreadId;
bool IsRunningOnWorkerThread()
{
  return GetCurrentThreadId() == g_WorkerThreadId;
}
bool LaunchWorkerThread()
{
 HANDLE hThread = CreateThread(nullptr, 0,
                               WorkerThread,
                               nullptr, 0,
                               &g_WorkerThreadId);
 if (hThread != nullptr) {
   CloseHandle(hThread);
   return true;
 }
 return false;
}
DWORD CALLBACK WorkerThread(void *Proc)
{
  // Can this assertion ever fire?
  assert(IsRunningOnWorkerThread());
  return 0;
}

Can the assertion at the start of WorkerThread
ever fire?

Naturally, the answer is Yes,
otherwise it wouldn’t be a very interesting article.

The assertion can fire if the worker thread starts running
before the call the Create­Thread returns.
In that case, the caller hasn’t yet received the
handle or ID of the newly-started thread.
The new thread calls
Is­Running­On­Worker­Thread,
which returns false since
g_Worker­Thread­Id hasn’t been initialized yet.

The actual bug was something along the lines of this:

void DoSomething()
{
  if (IsRunningOnWorkerThread()) {
     .. do it one way ..
  } else {
     .. do it the other way ..
  }
}
void DoManyThings()
{
  DoSomething();
  DoSomethingElse();
  DoYetAnotherThing();
}
DWORD CALLBACK WorkerThread(void *Proc)
{
  ...
  DoManyThings();
  ...
  return 0;
}

If the new thread started up so quickly that the original thread
doesn’t get a chance to receive the new thread ID and put
it into
g_Worker­Thread­ID,
then the Do­Something function
called from the worker thread will accidentally do things
the not-on-the-worker-thread way,
and then things start go go awry.

One way to address is is to add suspenders to your belt:

DWORD CALLBACK WorkerThread(void *Proc)
{
  g_WorkerThreadId = GetCurrentThreadId();
  ...

By having both the original thread and the created thread
set the g_WorkerThreadId variable,
you cover both cases of the race.
If the original thread runs faster, then the
CreateThread function will set the
g_WorkerThreadId variable to the ID of the worker
thread,
and the first line of Worker­Thread
will be redundant.
On the other hand, if the worker thread runs faster,
then the assignment at the beginning of
Worker­Thread sets the thread ID,
and the assignment performed by
the CreateThread function will be redundant.

Raymond Chen
Raymond Chen

Follow Raymond   

0 comments

Comments are closed.