【问题标题】:Problem with Mutex and Threads互斥锁和线程问题
【发布时间】:2011-09-15 11:21:10
【问题描述】:

我处于多线程情况,我有一个函数,我想一次只从一个线程运行。但是,不是以传统方式序列化函数,我希望在第一个线程运行它时尝试进入函数的任何线程立即返回。我不希望第二个线程等待第一个线程。

这是我的代码:

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,出现消息框,而消息框仍然显示(假设该功能仍在运行)我再次按下button1,没有任何显示(这是应该发生的)但是当我关闭消息框并再次按下按钮时,它什么也不显示。 (该函数应该再次运行,因为它没有运行:S)

【问题讨论】:

    标签: multithreading delphi mutex


    【解决方案1】:

    试试这个:

    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 &lt;&gt; 0) and not (GetLastError = ERROR_ALREADY_EXISTS)

    编辑:顺便说一句,这并不是互斥锁的真正用途。使用互斥锁的“传统”方式是创建它们,然后当您想要执行受互斥锁保护的代码时,让您的线程尝试获取它们的所有权。我希望 CreateMutex 比仅仅获得互斥锁的所有权要慢很多,而且该技术可能还有其他一些陷阱。

    【讨论】:

    • 谢谢,这完全完成了工作:D,那么你认为我应该在这种情况下使用什么?
    • Mutex 在这方面是非常重量级的。很难相信这是最好的解决方案。
    • @Ken 不需要。 TCriticalSection.TryEnter已经回答了。
    • @David,我同意你的看法。不过,问题是为什么他的代码没有按预期工作,而不是什么是更好的实现方式。我确实认为指出潜在的替代方案是恰当的。 OP 奖励是否回答他的问题或建议的替代方案由他自行决定。
    • @David,我不了解临界区的内部结构,但我认为这对于这项任务来说也可能是相当重量级的。此外,它不会像 OP 似乎需要的那样工作,因为他希望函数是不可调用的,只要之前的执行没有完成,即使之前的执行来自同一个线程。
    【解决方案2】:

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

    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 在 SyncObjs 单元的 TCriticalSection 对象中封装了临界区 API。

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

    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;
    

    【讨论】:

    • 关于联锁方法,我可能会改用InterlockedCompareExchange。你的方法有一个(尽管微不足道的)机会在不应该的情况下失去呼叫。
    • @ken 不,它不能丢失呼叫
    • 好吧,AFAIK,这个例子是可能的:线程1进入函数(取得所有权)。 Thread2 进入,只增加计数器。 Thread1 退出函数。 Thread3 进入函数(在 Thread2 退出之前)。即使函数不是“拥有”的,Thread3 也不能取得所有权,因为 Thread2 仍然对该函数拥有(假)所有权。
    • @Ken 好电话。如果可以挂断一些电话,那么可以在此状态下挂断。但分析得很好。喜欢这类问题。
    【解决方案3】:

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

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

    【讨论】:

    • +1 这可以正常工作。不过不需要FindAtom(),只需添加和删除即可。
    【解决方案4】:

    您应该创建一次互斥锁,并在您的线程运行期间一直持有它,然后让函数使用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;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-01-31
      • 2012-07-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多