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;
精彩评论