开发者

Problem with Mutex and Threads

开发者 https://www.devze.com 2023-04-04 21:07 出处:网络
I am in a multi-threaded situation and I have a function that I want to be run from only one thread at a time. However, rather than serializing the function in the tradition manner, I want any threads

I am in a multi-threaded situation and I have a function that I want to be run from only one thread at a time. However, rather than serializing the function in the tradition manner, I want any threads that attempt to enter the function whilst the first thread is running it to return immediately. I do not want the second thread to wait for the fir开发者_开发技巧st thread.

Here is my code:

function InitMutex(const Name:String; var Handle: THandle):Boolean;
begin
 Handle := CreateMutexA(NIL, True, PAnsiChar(Name));
 Result := not (GetLastError = ERROR_ALREADY_EXISTS);
end;


procedure TForm1.Button1Click(Sender: TObject);
var
 mHandle: THandle;
begin
 if not InitMutex(BalloonTipMutex, mHandle) then Exit;


 MessageBox(0, 'Executing Code....', '', 0);


 ReleaseMutex(mHandle);
 CloseHandle(mHandle);
end;

This is just an example with the same problem, cause I couldn't do a test sample with the threads.

The problem is: I click button1 for the first time, The messagebox appears, while the messagebox is still displayed (suppose the function is still running) I press button1 again, nothing is displayed (which is what's supposed to happen) but when I close the message box and press the button again, it shows nothing. (the function supposed to run again since its not running :S)


Try this instead:

procedure TForm1.Button1Click(Sender: TObject);
var  mHandle: THandle; 
begin   
  mHandle := 0;
  if InitMutex(BalloonTipMutex, mHandle) then 
  begin      
    MessageBox(0, 'Executing Code....', '', 0);
    ReleaseMutex(mHandle);  
  end;
  if handle <> 0 then
    CloseHandle(mHandle); 
end;

your problem is... Even if CreateMutex returns error ERROR_ALREADY_EXISTS, it did "open" the mutex. So when your first function exit, the mutex is not freed since your 2nd call opened it, but never closed it. So when you try to call your function a 3rd time, it fails not because your first call kept the mutex open, but because your 2nd call did.

Also, I think InitMutex should return Result := (Handle <> 0) and not (GetLastError = ERROR_ALREADY_EXISTS)

EDIT: On a side note, this isn't really the way mutex are meant to be used. The "traditional" way to use mutex is to create them, then have your thread try to get ownership of them when you want to execute the code protected by the mutex. I would expect CreateMutex to be quite a bit slower than just taking ownership of a mutex and maybe there are some other pitfalls to that technique.


Now that I finally understand the question, I believe that the most efficient solution is to use interlocked operations.

procedure OneAtATimeThroughHere;
//FLockCount is a properly aligned integer, shared between all threads
var
  ThisLockCount: Integer;
begin
  ThisLockCount := InterlockedIncrement(FLockCount);
  try
    if ThisLockCount=1 then//we won the race
    begin
      //do stuff
    end;
  finally
    InterlockedDecrement(FLockCount);
  end;
end;

This approach will not permit re-entrant calls. If you need to cater for re-entrant calls then the solution is to use TryEnterCriticalSection(). Critical sections are much easier to use than mutexes, and they are faster too. Delphi wraps up the critical section API in the TCriticalSection object in the SyncObjs unit.

So your code would look like this:

procedure OneAtATimeThroughHere;
//FLock is an instance of TCriticalSection shared between all threads
if FLock.TryEnter then
begin
  try
    //do stuff
  finally
    FLock.Release;
  end;
end;


As an alternate solution, you could use the AddAtom(), FindAtom() and DeleteAtom() Windows API functions (see: http://msdn.microsoft.com/en-us/library/ms649056(v=vs.85).aspx). There are also global versions of these for use between processes.

Using atoms would allow you to maintain full control over the flow of your threads and contain the entire locking mechanism within the function (like you could with a critical section).


You should create the mutex once and hold on to it for as long as your threads are running, and then have the function use WaitForSingleObject() with a timeout of 0 milliseconds to try to acquire the mutex lock. If WaitForSingleObject() returns WAIT_OBJECT_0, then the function was not already running yet.

var
  mHandle: THandle = 0;

procedure TForm1.FormCreate(Sender: TObject);
begin
  mHandle := CreateMutex(nil, False, nil);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  CloseHandle(mHandle);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  if WaitForSingleObject(mHandle, 0) = WAIT_OBJECT_0 then
  begin
    try
      MessageBox(0, 'Executing Code....', '', 0);
    finally
      ReleaseMutex(mHandle);
    end;
  end;
end;
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号