tags:

views:

178

answers:

2

I have a common resource, which I want 1 and only 1 instance of my application (or it's COM API) to have access to at any time. I have tried to protect this resource using mutexes, but when multiple threads of a host dotnet application try to access the COM object, the mutex doesn't seem to be released. This is the code I have used to protect my resource.

repeat
  Mutex := CreateMutex(nil, True, PChar('Connections'));
until (Mutex <> 0) and (GetLastError <> ERROR_ALREADY_EXISTS);
  try
    //use resource here!
  finally
    CloseHandle(Mutex);
  end;

If I run the threads simultaneously, the first thread get's through (obviously, being the first one to create the mutex), but subsequent threads are caught in the repeat loop. If I run each thread at 5 second intervals, then all is ok.

I suspect I'm not using mutexes correctly here, but I have found very little documentation about how to do this.

Any ideas?

+5  A: 

You're using the mutex wrong. You should be waiting for it and releasing it, not recreating it constantly.

During initialization:

Mutex := CreateMutex(nil, False, 'Connections');
if Mutex = 0 then
  RaiseLastOSError;

When you want to access the resource

if WaitForSingleObject(Mutex, INFINITE) <> WAIT_OBJECT_0 then
  RaiseLastOSError;
try
  // Use resource here
finally
  ReleaseMutex(Mutex)
end;

During finalization

CloseHandle(Mutex);

Also, since mutexes are global, you should pick something a little more unique than "connections" for the name. We added a GUID to the end of ours.

Craig Peterson
I should add that this only works if I set the second parameter of CreateMutex to True. Otherwise I get the same problem as before with hung threads, each waiting for each other.
Steve
Odd. The WaitForSingleObject call should acquire the mutex for you. The MSDN example uses False as well: http://msdn.microsoft.com/en-us/library/ms686927%28VS.85%29.aspx
Craig Peterson
Craig is right. My recommendation is to *never* pass `True` for the second parameter. The reason is that when the function returns, you have no idea whether you actually own the mutex. Furthermore, at creation time, you probably don't need to own the mutex anyway. Create the mutex *once* when your program starts. Then acquire and release it as your program runs. When your program terminates, close the handle. Do not create and destroy the mutex each time you need to own it.
Rob Kennedy
Ok, I can set the parameter to False and it works for a while but at some point I hang. And I'm not sure why?
Steve
If you do pass True you can tell whether you own the mutex by checking GetLastError. The second parameter is only used for newly created mutexes, so you only own it if GetLastError does not return ERROR_ALREADY_EXISTS. I wouldn't say never use it that way, but in general it is easier to just use WaitFor separately.
Craig Peterson
Steve, it would be difficult for us to say without seeing the code or knowing more about the situation. Once everything is hung you should look at the call stacks and see what's hung that shouldn't be. Perhaps add some extra state within the locked code so it can tell who should have the mutex at that point (and who may not have released it), and whether anything has tried to enter it twice.
Craig Peterson
+2  A: 

Try with this simple demo code. Start several instances of the application, and you can see from the background colour how they share the mutex:

procedure TForm1.FormCreate(Sender: TObject);
begin
  fMutex := SyncObjs.TMutex.Create(nil, False, 'GlobalUniqueName');
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  fMutex.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Color := clRed;
  Update;
  fMutex.Acquire;
  try
    Color := clGreen;
    Update;
    Sleep(5 * 1000);
  finally
    fMutex.Release;
  end;
  Color := clBtnFace;
end;

Note that I chose to use the TMutex class from the SyncObjs unit, which simplifies things.

mghie
This is a good answer too, but Craig posted first and he has less rep than you. I wish I could accept both as both are great answers.
Steve