【问题标题】:Multithreading issue while using Mutex in SerialCommunication C++在 SerialCommunication C++ 中使用 Mutex 时的多线程问题
【发布时间】:2024-01-03 00:12:01
【问题描述】:

我正在开发一个使用 windows 功能的串行通信软件。 在此 CSerialCommhelper 是处理所有串行通信功能的类,而 CphysicalLayer 是利用该类的类。

class CSerialCommHelper :
public CCommAgent
 {
    HANDLE m_pPortHandle;           //Handle to the COM port
    HANDLE m_hReadThread;           //Handle to the Read thread
    HANDLE m_hPortMutex;            //Handle to Port Mutex
    std::wstring m_strPortName;     //Portname
    COMMTIMEOUTS m_CommTimeouts;    //Communication Timeout Structure
    _DCB dcb;                       //Device Control Block
    DWORD m_dwThreadID;     //Thread ID

public:
    CSerialCommHelper(CPhysicalLayer *);
    virtual HRESULT Open();
    virtual HRESULT ConfigPort();
    static void * ReadThread(void *);
    virtual HRESULT Write(const unsigned char *,DWORD);
    virtual HRESULT Close();
    //virtual HRESULT Flush(DWORD dwFlag = PURGE_TXCLEAR | PURGE_RXCLEAR);
    wstring StringToWstring(const string &);
    ~CSerialCommHelper(void);
};

CommAgent 包含一个 CphysicalLayer 指针,用于在收到数据时通知physicalLayer。

HRESULT CSerialCommHelper::Write(const unsigned char *pucDataToWrite,DWORD ulLength)
{
    unsigned long  bytesWritten=0, ij = 0;
    WaitForSingleObject(m_hPortMutex,INFINITE);
    if(WriteFile(m_pPortHandle,pucDataToWrite,ulLength,&bytesWritten,NULL))
    {
    if(!ReleaseMutex(m_hPortMutex))
    {
        DWORD err=GetLastError();

        // Mutex released succesfully..
    }
    }
    if (bytesWritten != ulLength)
            return E_FAIL;
    return S_OK;

}
void * CSerialCommHelper::ReadThread(void * pObj)
{
    CSerialCommHelper *pCSerialCommHelper =(CSerialCommHelper *)pObj; 
    DWORD dwBytesTransferred = 0;
    unsigned char byte = 0;

    while (pCSerialCommHelper->m_pPortHandle != INVALID_HANDLE_VALUE)
    {
        pCSerialCommHelper->m_strBuffer.clear();
        pCSerialCommHelper->m_usBufSize=0;
        WaitForSingleObject(pCSerialCommHelper->m_hPortMutex,INFINITE);
        do
        {
            dwBytesTransferred = 0;
            ReadFile (pCSerialCommHelper->m_pPortHandle, &byte, 1, &dwBytesTransferred, 0);
            if (dwBytesTransferred == 1)
            {
                pCSerialCommHelper->m_strBuffer.push_back(byte);
                pCSerialCommHelper->m_usBufSize++;
                continue;

            }
        }
        while (dwBytesTransferred == 1);
        if(pCSerialCommHelper->m_usBufSize!=0)
        {
            CProtocolPacket *pCProtocolPacket = new CProtocolPacket(0,2048);
            pCProtocolPacket->AddBody(pCSerialCommHelper->m_usBufSize,(unsigned char*)pCSerialCommHelper->m_strBuffer.c_str());
            pCSerialCommHelper->m_pCPhysicalLayer->Data_ind(pCProtocolPacket);
            delete pCProtocolPacket;
        }
            ReleaseMutex(pCSerialCommHelper->m_hPortMutex);
        Sleep(2);
    }
    ExitThread(0);


    return 0;
}

这就是我创建文件和互斥锁的方式

    m_pPortHandle = CreateFile(m_strPortName.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL,
                         OPEN_EXISTING,NULL, NULL );
if (m_pPortHandle == INVALID_HANDLE_VALUE)

    {
        return E_HANDLE;
        //throw failure
    }

m_hPortMutex = CreateMutex(NULL,TRUE,L"MY_MUTEX");


if( m_hReadThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ReadThread,(LPVOID)this,0,&m_dwThreadID))
{
}
else
{
    return E_FAIL;
}
return S_OK;

但是在向端口写入字符串后,写入函数正在成功释放互斥锁,但读取线程仍在等待。

【问题讨论】:

  • 为什么要使用互斥锁保护具有独立 tx 和 rx 通道的双向端口?另外,“睡眠(2);” :((
  • 另外,使用系统调用一次读取一个字节:((
  • 该软件用于向设备发送命令。发送一条命令后,PC 必须等待响应。为了防止任何人在收到响应之前写入端口,我正在使用互斥锁。
  • 嗯...建议..如果协议始终是请求/响应,那么将“RequestResponse”类实例排队可能会更容易,其成员可以保存/生成请求并解析回复, 到一个既发送响应又等待回复的线程?
  • 哦等等 - 你正在命名互斥体。不确定命名互斥体的生命周期。需要检查。通常,不命名线程间通信互斥锁。

标签: windows multithreading visual-c++ serial-port mutex


【解决方案1】:

创建互斥体时,将 bInitialOwner 参数设置为 TRUE。所以此时互斥锁归主线程所有。

m_hPortMutex = CreateMutex(NULL,TRUE,L"MY_MUTEX");

然后您创建一个ReadThread 来尝试获取互斥锁。这显然会阻塞,直到主线程释放它。

WaitForSingleObject(pCSerialCommHelper->m_hPortMutex,INFINITE);

当主线程试图写东西时,它做的第一件事就是再次尝试获取互斥体。

WaitForSingleObject(m_hPortMutex,INFINITE);

由于主线程已经拥有互斥锁,此调用将立即返回而不会阻塞,但在这个阶段,您现在已经在主线程中获得了两次互斥锁(一次在 CreateMutex 调用中,第二次在 @ 987654327@).

当您完成对文件的写入后,然后通过此调用释放互斥锁:

if(!ReleaseMutex(m_hPortMutex))

但这只会释放一次,所以它仍然归主线程所有,读取线程将继续阻塞。

底线是你应该在创建互斥体时将 bInitialOwner 参数设置为 FALSE。

m_hPortMutex = CreateMutex(NULL,FALSE,L"MY_MUTEX");

引用CreateMutex documentation

拥有互斥锁的线程可以在重复的等待函数调用中指定相同的互斥锁,而不会阻塞其执行。 [...] 但是,要释放其所有权,线程必须在互斥锁每次满足等待时调用 ReleaseMutex 一次。

【讨论】:

  • 我已根据您的回答更新了更改。但是现在写入函数无法获取互斥锁。 :(
  • WaitforSingleobject() 将能够获取互斥锁,因为它不属于任何人,对吧?但它无法做到这一点!
  • 它应该能够,除非ReadThread 已经获取了互斥锁并且在它调用ReleaseMutex 之前被阻塞。暂停调试器中的代码,看看ReadThread 在做什么。
  • 我调试了代码,我发现,readThread 只执行到 Readfile()。之后它将在主线程中 write() 并卡在那里......
  • 我尝试评论 Readfile() ,然后一切正常。
【解决方案2】:

在您的代码中,我看到以下问题:

  • 互斥锁最初归调用构造函数的线程所有。
  • Write() 出错时不会释放互斥锁。

我猜你是从与构造函数调用相同的线程调用Write(),因为它已经拥有互斥锁。此后,互斥锁仍归线程所有。使另一个线程阻塞。

我建议您将发布调用移到检查写入是否成功(因为您总是需要释放它)之外。

您应该注意,每次获取互斥体时都需要调用一次ReleaseMutex(),通过CreateMutex()bInitialOwner 设置为TRUE,或者通过成功调用互斥体上的WaitForSingleObject()

【讨论】:

  • 我逐行调试代码,发现ReleaseMutex()函数执行成功。然后线程也无法获得互斥锁的所有权。
  • 在您的CreateMutex() 电话之后是否有ReleaseMutex() 电话?您的参数指定应该获取互斥锁。
  • 不,我写代码是为了调试。我正在删除它。
  • @kernel Hasturkun 是正确的。主要问题是使用 TRUE 调用 CreateMutex - 我试图在 my answer 中更详细地解释。
【解决方案3】:

问题在于 ReadThread 中的 Readfile 函数。它没有正确返回。 不知道是什么原因。

void * CSerialCommHelper::ReadThread(void * pObj)
{
    CSerialCommHelper *pCSerialCommHelper =(CSerialCommHelper *)pObj; 
    DWORD dwBytesTransferred =0;
    char byte[1];
    string buffer;

    while (pCSerialCommHelper->m_pPortHandle != INVALID_HANDLE_VALUE)
    {

        WaitForSingleObject(pCSerialCommHelper->m_hPortMutex,INFINITE);
        do
        {
           dwBytesTransferred = 0;
                bool bReadResult= false;

                 bReadResult = ReadFile (pCSerialCommHelper->m_pPortHandle,byte,1,&dwBytesTransferred,NULL);


                if (dwBytesTransferred == 1)
                {
                    buffer.push_back(byte[0]);
                    continue;

                }


        }
        while (dwBytesTransferred == 1);

            pCSerialCommHelper->m_pCPhysicalLayer->Data_ind((unsigned char *)buffer.c_str());
            ReleaseMutex(pCSerialCommHelper->m_hPortMutex);

    }
    ExitThread(0);


    return 0;
}

我尝试用这个 sn-p 替换代码,发现 bReadResult 没有正确更新。

【讨论】:

  • 问题是 COMMTIMOUT 结构元素设置不正确。设置后该代码工作正常。
【解决方案4】:

尝试创建没有名称的互斥锁。命名互斥锁通常用于进程间通信。通常,用于进程内线程之间通信的互斥锁没有命名 - 它们不需要按名称打开,因为所有进程线程都可以访问互斥锁句柄。

【讨论】: