2011-09-15 130 views
2

我在多线程的情况下,我有一个函数,我想一次只从一个线程运行。但是,我不想以传统的方式序列化函数,而是想让所有尝试进入函数的线程在第一个线程运行时立即返回。我不希望第二个线程等待第一个线程。互斥锁和线程问题

这里是我的代码:

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; 

这仅仅是同样的问题为例,因为我不能做与线程的测试样品。

问题是:我第一次点击button1时出现messagebox,而messagebox仍然显示(假设函数仍在运行)我再次按下button1,没有任何显示(这是应该发生的)但是当我关闭消息框并再次按下按钮时,它什么也不显示。 (所谓的功能,因为它没有运行再次运行:S)

回答

3

试试这个:

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; 

你的问题是...即使CreateMutex返回错误ERROR_ALREADY_EXISTS,它没有“开放”的互斥体。所以当你的第一个函数退出时,互斥体并没有被释放,因为你的第二次调用打开了它,但从未关闭它。所以当你第三次尝试调用你的函数时,它并不会因为你的第一次调用保持互斥体打开,而是因为你的第二次调用了。

另外,我觉得InitMutex应该返回Result := (Handle <> 0) and not (GetLastError = ERROR_ALREADY_EXISTS)

编辑:在一个侧面说明,这是不是真的互斥方式是为了使用。使用互斥锁的“传统”方法是创建它们,然后让您的线程在您想要执行由互斥锁保护的代码时尝试获取它们的所有权。我期望CreateMutex比仅仅拥有一个互斥锁要慢得多,也许这种技术还有一些其他的缺陷。

+0

谢谢,那完全没有工作:D,那么你认为我应该在这种情况下使用什么? – killercode

+0

互斥量对此非常重量级。很难相信这是最好的解决方案。 –

+0

@Killercode,我建议你为此询问一个单独的问题。 –

3

现在我终于明白了这个问题,我相信最有效的解决方案就是使用互锁操作。

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; 

该方法不允许重入呼叫。如果您需要迎合重新拨入电话,那么解决方案是使用TryEnterCriticalSection()。关键部分比互斥更容易使用,而且速度也更快。 Delphi将关键部分API包装在SyncObjs单元的TCriticalSection对象中。

所以,你的代码应该是这样的:

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; 
+0

关于联锁方法,我可能会使用'InterlockedCompareExchange'来代替。否则,你的方法有(尽管不重要)失去呼叫的机会。 –

+0

@ken不,它不能失去电话 –

+0

那么,AFAIK,这个例子是可能的:Thread1输入函数(取得所有权)。线程2进入,只增加计数器。 Thread1退出函数。线程3进入函数(在线程2退出之前)。即使函数不是“拥有”,Thread3也不能拥有所有权,因为Thread2对函数仍具有(false)所有权。 –

3

作为一个替代的解决方案,你可以使用AddAtom()FindAtom()DeleteAtom() Windows API函数(见:http://msdn.microsoft.com/en-us/library/ms649056(v=vs.85).aspx)。还有这些在进程之间使用的全局版本。

使用原子将允许您保持对线程流的完全控制,并在函数内包含整个锁定机制(就像您可以使用关键部分一样)。

+0

+1这将工作正常。不需要FindAtom(),只需添加和删除即可。 –

1

只要线程正在运行,您应该创建一次互斥锁,然后让函数使用WaitForSingleObject(),其超时时间为0毫秒,以尝试获取互斥锁。如果WaitForSingleObject()返回WAIT_OBJECT_0,那么该函数尚未运行。

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;