Click here to Skip to main content
Click here to Skip to main content
Go to top

An interruptible mutex class

, 6 Aug 2004
Rate this:
Please Sign up or sign in to vote.
How to safely terminate a thread waiting on a mutex
<!-- Add the rest of your HTML here -->

Introduction

As developers we're rightly concerned that our applications keep running. So concerned, in fact, that it's easy to forget that a user will judge our work not just by how well it runs but also by how easily they can stop it running. Dunno about you but when I hit the close button on an application I expect it to go away now. It's acceptable that it ask me if I want to save changes I've made but it's not acceptable that it just hangs around, seemingly indefinitely. Even if the UI goes away I'll probably notice the app is still there in task manager if it takes a while to exit. (In passing I note that Visual Studio sometimes seems to take forever to terminate - hanging around long after the UI has been torn down. Interestingly enough, when it does this it consumes almost no CPU cycles. What follows is part of my educated guesses as to the reason why).

Background

Much of the work I've been doing lately involves multiple threads. I've already covered, in other articles, some of the issues involved with using threads whilst being able to stop them at will, such as using overlapped I/O[^] in order to make an I/O thread stoppable and the need to make sure a thread is cognizant of external events in this article[^ ].

Recently I ran into another issue with stopping threads. The issue involved synchronising access to a shared resource.

Sharing resources

We all know the theory. If two or more threads are sharing a resource they should implement some kind of mediation mechanism in order to be sure that changes made by a thread are completed and the shared resource returns to a stable state before another thread gains access. For example, if one thread is adding records to the tail of a linked list it's probably not a good idea to have another thread simultaneously removing records from the head of that linked list. That way memory corruption lies! Thus it behooves the careful programmer to implement locking around the operations on the linked list. Thread A aquires the lock or waits until it can aquire the lock. Once it's got the lock it performs the work it needs to do on the linked list and then releases the lock. If Thread B wants to perform operations on the linked list while Thread A has the lock it waits until the lock is released. Once the lock is released thread B has a chance to grab the lock and do it's work.

Mutexes

One way to implement the mediation mechanism is to use a mutex. The name is a contraction of mutual exclusion. One entity, and one entity alone, can own a mutex. If you own a mutex no one else can own it. As long as someone else owns it you can't. If everyone who wants to perform work on a shared resource agrees that such work can only be done when a mutex protecting the shared resource is owned by that worker the problem is solved.

You create a mutex on the Windows platform by calling the CreateMutex function, which returns a handle to a mutex object. You aquire ownership of the mutex either by specifying that you own it when you create it, or by waiting on it. If the mutex isn't owned by anyone else when you wait on it you get ownership, otherwise you wait until it's been released by the owner. And finally, when you've finished with owning the mutex you release it.

The problem

If you've read my article about using overlapped I/O you know what the problem is. The problem is that the classic way of waiting on a mutex is to use WaitForSingleObject() which, as the name suggests, waits on a single object (the mutex handle). The wait is, of course, done deep in the bowels of the operating system in code you didn't write and don't control. There are only two ways for the call to return. Either the object is signalled (we gained ownership of the mutex) or the timeout has elapsed. I might write a thread thusly,

unsigned __stdcall ThreadProc(LPVOID data)
{
    // Cast data to some type relevant to the context
    // and do some preliminary work. Then grab the mutex.
    WaitForSingleObject(gbl_hMutex, INFINITE);
    
    // Now we've got the mutex, do some work and then release it.
    ReleaseMutex(gbl_hMutex);
}
        

Now obviously this thread procedure makes no provision for the possibility that it might need to be externally terminated. The only way you're going to kill this thread, once it's waiting on the mutex, is to make sure the mutex is released so this thread can gain ownership of it (and thus break out of the WaitForSingleObject()) call, or use TerminateThread(), which is a course fraught with problems unless you're terminating the entire process. What if you want to terminate only this thread yet keep the entire process running?

The first approach might be to do something like this.

unsigned __stdcall ThreadProc(LPVOID data)
{
    bool bStayInLoop = true;
    
    while (bStayInLoop)
    {
        //  Do some preliminary work then grab the mutex
        switch (WaitForSingleObject(gbl_hMutex, 100))
        {
        case WAIT_OBJECT_0:
            //  We got the mutex, break out of the while loop
            bStayInLoop = false;
            break;
            
        case WAIT_TIMEOUT:
            //  We timed out, check if we should terminate the
            //  thread.
            if (gbl_exitThread)
                //  It's terminate  time, exit the thread
                return 0;
        }
    } 
    
    // Now we've got the mutex, do some work and then release  it.
    ReleaseMutex(gbl_hMutex);
}
        
which waits a short time for the mutex, times out if it didn't get the mutex, checks if it should exit and if not repeats the entire process. This takes a some educated guessing to get it right. Set the timeout too low and you waste CPU cycles checking if your thread should exit. Set the timeout too high and the thread takes too long to terminate itself.

A better approach

is to use the WaitForMultipleObjects() call passing two handles. One handle is the handle to the mutex you want to own, the other handle is to an event object which can be signalled when you want the thread to terminate. The code would look something like this.

unsigned __stdcall ThreadProc(LPVOID data)
{
    //  Do some preliminary work then grab the mutex
    
    HANDLE hArray[2] = { gbl_stopEvent, gbl_hMutex };
    
    switch (WaitForMultipleObjects(2, hArray, FALSE, INFINITE))
    {
    case WAIT_OBJECT_0:
        //  We've been signalled to exit, so exit
        return 0;

    case WAIT_OBJECT_0 + 1:
        //  We got the mutex
        break;
    }
    
    // Now we've got the mutex, do some work and then release  it.
    ReleaseMutex(gbl_hMutex);
}

Now you can wait for the mutex to become available, safely terminate the thread when requested and do both without wasting any CPU cycles. Naturally I want to encapsulate at least part of this into a class I can reuse.

CInterruptibleMutex

Is a class that handles the creation and destruction of the mutex and performs the wait on your behalf. The class looks like this.
class CInterruptibleMutex
{
public:
    enum eMutexState
    {
        eStopped,
        eMutexAquired,
        eTimedOut,
    };
                    CInterruptibleMutex();
    virtual         ~CInterruptibleMutex(void);

    bool            IsValid() const
                    { return m_hMutex != INVALID_HANDLE_VALUE; }

    eMutexState     AquireMutex(HANDLE hStopEvent, 
                                DWORD dwTimeout = INFINITE);
    void            ReleaseMutex() const  { ::ReleaseMutex(m_hMutex); }

private:
    HANDLE          m_hMutex;
};
        

which looks pretty straightforward. The constructor creates the mutex object, initially unowned by anyone. The destructor closes the mutex handle. The IsValid() function can be used to check that it was possible to create the mutex object controlled by the class. This will only fail if Windows was unable to allocate a handle, and if that's the case your program probably has little chance of running successfully anyway!

AquireMutex() takes a handle to an event which is used to interrupt the call if required; You can specify a timeout that defaults to INFINITE.

The AquireMutex() function looks like this.
CInterruptibleMutex::eMutexState CInterruptibleMutex::AquireMutex(
                                        HANDLE hStopEvent, DWORD dwTimeout)
{
    assert(IsValid());

    HANDLE hArray[2] = { hStopEvent, m_hMutex };

    switch (WaitForMultipleObjects(2, hArray, FALSE, dwTimeout))
    {
    case WAIT_OBJECT_0:
        return eStopped;

    case WAIT_OBJECT_0 + 1:
        return eMutexAquired;

    default:
        return eTimedOut;
    }
}
which creates a handle array and calls WaitForMultipleObjects() . The call returns the appropriate value from the eMutexState enumeration so that calling code can determine an appropriate course of action. Pretty simple.

Using the code

Add the files in the download to your project and then instantiate a CInterruptibleMutex object where you'd normally use a HANDLE to a mutex. Create an event object somewhere in your code and make sure it's a manual reset event. Then call the AquireMutex() function passing the event handle. If you need to terminate the AquireMutex() call before it aquires ownership of the mutex you signal the event handle. Of course, the calling function has to take account of the return value from AquireMutex() but most of my usage of the class has specified an infinite timeout so I never need worry about the possibility of it returning eTimedOut (I should live so long...). Thus, in the usual case, a call to AquireMutex() can be as simple as,

if (m_myMutex.AquireMutex(m_hStopEvent) == CInterruptibleMutex::eStopped)
    //  We were stopped, exit the thread
    return 0;

History

August 7th, 2004 - Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Rob Manderson

United States United States
I've been programming for 35 years - started in machine language on the National Semiconductor SC/MP chip, moved via the 8080 to the Z80 - graduated through HP Rocky Mountain Basic and HPL - then to C and C++ and now C#.
 
I used (30 or so years ago when I worked for Hewlett Packard) to repair HP Oscilloscopes and Spectrum Analysers - for a while there I was the one repairing DC to daylight SpecAns in the Asia Pacific area.
 
Afterward I was the fourth team member added to the Australia Post EPOS project at Unisys Australia. We grew to become an A$400 million project. I wrote a few device drivers for the project under Microsoft OS/2 v 1.3 - did hardware qualification and was part of the rollout team dealing directly with the customer.
 
Born and bred in Melbourne Australia, now living in Scottsdale Arizona USA, became a US Citizen on September 29th, 2006.
 
I work for a medical insurance broker, learning how to create ASP.NET websites in VB.Net and C#. It's all good.
 
Oh, I'm also a Kentucky Colonel. http://www.kycolonels.org

Comments and Discussions

 
GeneralThat's all what I want PinmemberSahbi19-Jan-05 4:20 
GeneralA different approach PinmemberToby Opferman8-Aug-04 15:23 
GeneralOnly advantage of mutexes? PinmemberNathan Holt at CCEI17-Aug-04 5:57 
GeneralRe: Only advantage of mutexes? PinmemberToby Opferman18-Aug-04 4:54 
GeneralRe: Only advantage of mutexes? PinmemberNathan Holt at CCEI18-Aug-04 5:51 
GeneralRe: Only advantage of mutexes? PinmemberToby Opferman18-Aug-04 7:52 
GeneralRe: Only advantage of mutexes? PinmemberToby Opferman18-Aug-04 8:00 
Here's an example of where timeouts just weren't enough or even the right choice.
 
Someone performed some operation that sent data to a client. This situation you obviously will need timeouts if you want to wait, so obviously an event was used. So, the thread held onto the IRP and did a wait loop. This wait loop waited on an event with a timeout. Another thread would recieve data and then set the event to signal to the thread it was ok to exit.
 
Seems fine, right? Well, what if the time out had expired and the IRP became completed? The network roundtrip could have taken a long time and when it came in, perhaps it tried to use the IRP. This did happen eventually.
 
I solved it with a 3 way handshake using a mutex that didn't have a timeout, since it's rediculous to timeout on the mutex, the event yes, mutex no.
 
The 3 state was NOTHING, WAITING, PROCESSING.
 
So, once the data had been sent to the client machine, the state when to "WAITING". Now, only the mutex can change the state. That's all the mutex did, change the state or check the state, nothing more. Now, if the timeout occurs, if the state is "WAITING" we can clear the IRP and set it to "NOTHING". If the timeout occured and the state is "PROCESSING", we continue to wait.
 
In the other thread, when we recieved data, if the state was "NOTHING", we exit. If the state is "WAITING" we switch to "PROCESSING" and leave the mutex. We then do work, and finally signal the event and possibly switch the state to "NOTHING".
 
Now, what happens if something goes wrong during the processing of data or traps? Nothing should go wrong and all exit points should signal the event. Also, since this was in the kernel, a trap would mean a bluescreen so, a timeout is worthless anyway.
 
The main point is, use the type of object that is right for the job. A mutex is not always the right type of object. If you want to perform waits and timeouts, events are much better suited.

GeneralRe: Only advantage of mutexes? PinmemberNathan Holt at CCEI19-Aug-04 6:10 
GeneralRe: Only advantage of mutexes? PinmemberToby Opferman19-Aug-04 13:03 
GeneralRe: Only advantage of mutexes? PinmemberNathan Holt at CCEI20-Aug-04 5:04 
GeneralRe: Only advantage of mutexes? PinmemberToby Opferman21-Aug-04 7:46 
GeneralRe: Only advantage of mutexes? PinmemberToby Opferman21-Aug-04 8:28 
GeneralRe: Only advantage of mutexes? PinmemberNathan Holt at CCEI23-Aug-04 6:15 
GeneralRe: Only advantage of mutexes? PinmemberToby Opferman23-Aug-04 15:10 
GeneralRe: Only advantage of mutexes? PinmemberNathan Holt at CCEI26-Aug-04 6:12 
GeneralRe: Only advantage of mutexes? PinmemberToby Opferman26-Aug-04 17:47 
GeneralRe: Only advantage of mutexes? PinmemberNathan Holt at CCEI1-Sep-04 6:39 
GeneralSubject name is a bit misleading Pinmembersgenie8-Aug-04 11:41 
GeneralRe: Subject name is a bit misleading PinprotectorRob Manderson8-Aug-04 12:10 
GeneralRe: Subject name is a bit misleading Pinmembersgenie8-Aug-04 12:18 
GeneralRe: Subject name is a bit misleading PinprotectorRob Manderson8-Aug-04 13:06 
GeneralRe: Subject name is a bit misleading Pinmembersgenie8-Aug-04 13:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 7 Aug 2004
Article Copyright 2004 by Rob Manderson
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid