views:

83

answers:

2

I'm struggling with multi-threaded programming...

I have an application that talks to an external device via a CAN to USB module. I've got the application talking on the CAN bus just fine, but there is a requirement for the application to transmit a "heartbeat" message every second.

This sounds like a perfect time to use threads, so I created a thread that wakes up every second and sends the heartbeat. The problem I'm having is sharing the CAN bus interface. The heartbeat must only be sent when the bus is idle. How do I share the resource?

Here is pseudo code showing what I have so far:

TMainThread
{
    Init:
        CanBusApi =new TCanBusApi;
        MutexMain =CreateMutex( "CanBusApiMutexName" );

        HeartbeatThread =new THeartbeatThread( CanBusApi );

    Execution:
        WaitForSingleObject( MutexMain );
        CanBusApi->DoSomething();
        ReleaseMutex( MutexMain );
}

THeartbeatThread( CanBusApi )
{
    Init:
        MutexHeart =CreateMutex( "CanBusApiMutexName" );

    Execution:
        Sleep( 1000 );
        WaitForSingleObject( MutexHeart );
        CanBusApi->DoHeartBeat();
        ReleaseMutex( MutexHeart );
}

The problem I'm seeing is that when DoHeartBeat is called, it causes the main thread to block while waiting for MutexMain as expected, but DoHeartBeat also stops. DoHeartBeat doesn't complete until after WaitForSingleObject(MutexMain) times out in failure.

Does DoHeartBeat execute in the context of the MainThread or HeartBeatThread? It seems to be executing in MainThread.

What am I doing wrong? Is there a better way?

Thanks, David

A: 

I suspect that the CAN bus API is single-threaded under the covers. It may be marshaling your DoHeartBeat() request from your second thread back to the main thread. In that case, there would be no way for it to succeed since your main thread is blocked. You can fix this in basically two ways: (1) send a message to the main thread, telling it to do the heart beat, rather than doing it on the second thread; or (2) use a timer on the main thread for your heart beat instead of a second thread. (I do think that multithreading is overkill for this particular problem.)

Peter Ruderman
I think it is true that the CAN bus API is single-threaded internally and it does act like DoHeartBeat() is executing in the context of MainThread, but I'm not sure how to prove it. Is it generally true that object methods execute in the thread that created the object even when called from a different thread?
David
My motivation for using threads is to simplify my process state machine. With the heartbeat handled in a separate thread, my state machine can ignore the strict heartbeat timing requirements. If I use a single thread, I have to create a mechanism for knowing when the bus is in use, which is exactly what a mutex does. It seemed best to not re-implement this concept.
David
When you call an object method, the method executes in the context of the thread that called it, but that says nothing about what the method will *do*. If the CAN bus API is implemented using an apartment threaded COM server, then under the covers, DoHeartBeat() will send a window message to your main thread and instruct it to do the work. You'll have to do some research if this is the case. The good news is that if the API does use an apartment-threaded COM server, then you don't need the mutex at all.
Peter Ruderman
A: 

First, re-read the specs about the heartbeat. Does it say that an actual heartbeat message must be received every second, or is it necessary that some message be received every second, and that a heartbeat should be used if no other messages are in flight? The presence of data on the channel is de-facto evidence that the communications channel is alive, so no specific heartbeat message should be required.

If an actual heartbeat message is required, and it's required every second, in the above code there should be only one mutex and both threads need to share it. The code as written creates two separate mutexes, so neither will actually block. You'll end up with a collision on the channel and Bad Things Will Happen in CanBusApi. Make MainMutex visible a global/class variable and have both threads reference it.

jfawcett
The heartbeat is required every second, regardless of bus traffic. It keeps the receiving device in "Test Mode".
David
I'm working in MS Windows. It's my understanding that CreateMutex() only creates a new mutex if it doesn't find one with the specified name. Otherwise it returns a handle to the existing mutex. Am I mistaken?
David
No, you are correct. Calling CreateMutex twice with the same name returns two handles to the same object.
Peter Ruderman
Then crossing the 1-second limit is the problem. If you can be guaranteed that they'll all take less than 1 second, send a HB before and after every message no matter how long it took and still have the 1-second thread. In the 1-second thread, the sleep time needs to be 1 sec - time spent waiting. You'll be spamming HB messages, but you won't miss the 1-second crossover, which you will in the code above.
jfawcett