Mutex不能像我预期的那样工作

时间:2017-11-08 06:20:12

标签: mutex c++builder

我的环境:C ++ Builder XE4。

我正在使用Mutex。在下面的代码中,我预计当Timer1获取互斥锁时,将跳过Timer2进程。但是,根本没有跳过Timer2进程。

代码中有什么问题?

Unit1.cpp

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------

String MutexName = L"Project1";
HANDLE HWNDMutex;

void __fastcall TForm1::FormShow(TObject *Sender)
{
    HWNDMutex = CreateMutex(NULL, false, MutexName.c_str());
    if (HWNDMutex == NULL) {
        String msg = L"failed to create mutex";
        OutputDebugString(msg.c_str());
    }

    Timer1->Enabled = false;
    Timer1->Interval = 1000; // msec
    Timer1->Enabled = true;

    Timer2->Enabled = false;
    Timer2->Interval =  200; // msec
    Timer2->Enabled = true;
}

__fastcall TForm1::~TForm1()
{
    CloseHandle(HWNDMutex);
}

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
    if (WaitForSingleObject(HWNDMutex, INFINITE) == WAIT_TIMEOUT) {
        return;
    }

    if (CHK_update->Checked) {
        String msg = L"Timer1 " + Now().FormatString(L"yyyy/mm/dd hh:nn:ss.zzz");
        Memo1->Lines->Add(msg);
    }

    for(int loop=0; loop<10; loop++) {
        Application->ProcessMessages();
        Sleep(90); // msec
    }

    ReleaseMutex(HWNDMutex);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer2Timer(TObject *Sender)
{
    if (WaitForSingleObject(HWNDMutex, INFINITE) == WAIT_TIMEOUT) {
        return;
    }

    if (CHK_update->Checked) {
        String msg = L">>>Timer2 " + Now().FormatString(L"yyyy/mm/dd hh:nn:ss.zzz");
        Memo1->Lines->Add(msg);
    }

    ReleaseMutex(HWNDMutex);
}
//---------------------------------------------------------------------------

结果

Timer1 2017/11/08 15:20:39.781
>>>Timer2 2017/11/08 15:20:39.786
>>>Timer2 2017/11/08 15:20:40.058
>>>Timer2 2017/11/08 15:20:40.241
>>>Timer2 2017/11/08 15:20:40.423
>>>Timer2 2017/11/08 15:20:40.603
Timer1 2017/11/08 15:20:40.796
>>>Timer2 2017/11/08 15:20:40.799
>>>Timer2 2017/11/08 15:20:41.071
>>>Timer2 2017/11/08 15:20:41.254
>>>Timer2 2017/11/08 15:20:41.436
>>>Timer2 2017/11/08 15:20:41.619
Timer1 2017/11/08 15:20:41.810
>>>Timer2 2017/11/08 15:20:41.811
>>>Timer2 2017/11/08 15:20:42.083
>>>Timer2 2017/11/08 15:20:42.265
>>>Timer2 2017/11/08 15:20:42.448
>>>Timer2 2017/11/08 15:20:42.633

我尝试将TMutex与acquire()和release()一起使用,但它也没有用。

1 个答案:

答案 0 :(得分:2)

互斥锁具有线程关联性,因此是可重入的:

  

互斥对象是一个同步对象,其状态设置为在不由任何线程拥有时发出信号,并且在拥有时不发信号。一次只有一个线程可以拥有一个互斥对象,其名称来自于协调对共享资源的互斥访问这一事实。例如,为了防止两个线程同时写入共享内存,每个线程在执行访问内存的代码之前等待互斥对象的所有权。写入共享内存后,该线程释放互斥对象。

     

...

     

在线程获得互斥锁的所有权后,它可以在重复调用wait-functions时指定相同的互斥锁,而不会阻止其执行。这可以防止线程在等待它已拥有的互斥锁时自行死锁。要在这种情况下释放其所有权,每次互斥锁满足等待函数的条件时,线程必须调用一次ReleaseMutex。

TTimer是基于消息的计时器。你有两个计时器在同一个线程中运行。这意味着他们的OnTimer事件默认相对于彼此进行序列化。一次只能运行一个事件(除非你做一些愚蠢的事情,如调用Application->ProcessMessages(),这是一个可重入的噩梦)。

Timer2将首先触发(实际上是4-5次),每次在Timer1触发之前获取释放互斥锁。然后Timer1触发,获取锁,运行循环以抽取主UI消息队列,从而允许Timer2Timer1Timer()仍在运行时再次(多次)触发。 Timer2将重新获取释放UI线程已拥有的相同锁定,因此WaitForSingleObject()会立即退出WAIT_OBJECT_0。然后循环结束,Timer1释放锁。

此代码中的互斥锁无用。互斥锁用于线程间同步,但此代码中没有工作线程!您有一个单独的线程与自身同步,这是多余的,并且正是许多同步对象通过支持重新进入而避免的死锁情况。

一个关键部分也具有线程亲和力并且是可重入的,所以这对你没有帮助:

  

临界区对象提供类似于互斥对象提供的同步,但临界区只能由单个进程的线程使用。

     

...

     

当线程拥有一个关键部分时,它可以对EnterCriticalSection或TryEnterCriticalSection进行额外调用,而不会阻止其执行。这可以防止线程在等待它已经拥有的关键部分时自行死锁。要释放其所有权,线程必须在每次进入临界区时调用LeaveCriticalSection一次。无法保证等待线程获得关键部分所有权的顺序。

但是,信号量可以用于您正在尝试的内容,因为它没有线程关联:

  

信号量对象是一个同步对象,它维持零到指定最大值之间的计数。每次线程完成对信号量对象的等待时计数递减,并且每次线程释放信号量时递增。当计数达到零时,没有更多线程可以成功等待信号量对象状态变为信号。信号量的状态设置为当其计数大于零时发出信号,并且当其计数为零时不发信号。

     

信号量对象在控制可支持有限数量用户的共享资源时非常有用。它充当一个门,将共享资源的线程数限制为指定的最大数量。例如,应用程序可能会限制它创建的窗口数。它使用最大计数等于窗口限制的信号量,每当窗口创建时递减计数,并在窗口关闭时递增计数。应用程序在创建每个窗口之前指定调用其中一个等待函数的信号量对象。当计数为零时 - 表示已达到窗口限制 - 等待功能阻止执行窗口创建代码。

     

...

     

拥有互斥锁对象的线程可以重复等待同一个互斥锁对象发出信号,而不会阻止其执行。但是,每次等待操作完成时,重复等待同一个信号量对象的线程会减少信号量的计数。当计数变为零时,线程被阻塞。类似地,只有拥有互斥锁的线程才能成功调用ReleaseMutex函数,尽管任何线程都可以使用ReleaseSemaphore来增加信号量对象的数量。

如果切换到信号量,由于您使用Application->ProcessMessages()超时,一旦调用INFINITE并且信号量计数器降至0,您所显示的代码就会自动死锁。因此,使用较小的超时来防止这种情况发生。

试试这个:

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------

HANDLE hSemaphore;

void __fastcall TForm1::FormShow(TObject *Sender)
{
    hSemaphore = CreateSemaphore(NULL, 1, 1, NULL);
    if (hSemaphore == NULL) {
        OutputDebugString(L"failed to create semaphore");
    }

    Timer1->Enabled = false;
    Timer1->Interval = 1000; // msec
    Timer1->Enabled = true;

    Timer2->Enabled = false;
    Timer2->Interval =  200; // msec
    Timer2->Enabled = true;
}

__fastcall TForm1::~TForm1()
{
    if (hSemaphore)
        CloseHandle(hSemaphore);
}

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
    if (WaitForSingleObject(hSemaphore, 0) != WAIT_OBJECT_0) {
        return;
    }

    if (CHK_update->Checked) {
        String msg = L"Timer1 " + Now().FormatString(L"yyyy/mm/dd hh:nn:ss.zzz");
        Memo1->Lines->Add(msg);
    }

    for(int loop=0; loop<10; loop++) {
        Application->ProcessMessages();
        Sleep(90); // msec
    }

    ReleaseSemaphore(hSemaphore, 1, NULL);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer2Timer(TObject *Sender)
{
    if (WaitForSingleObject(hSemaphore, 0) != WAIT_OBJECT_0) {
        return;
    }

    if (CHK_update->Checked) {
        String msg = L">>>Timer2 " + Now().FormatString(L"yyyy/mm/dd hh:nn:ss.zzz");
        Memo1->Lines->Add(msg);
    }

    ReleaseSemaphore(hSemaphore, 1, NULL);
}
//---------------------------------------------------------------------------

旁注:注意为基于内核的同步对象提供名称。这允许其他进程访问它并使其背后的状态变得混乱。不要命名您不希望跨进程边界共享的对象!互斥体和信号量是可以使用的对象。