在一个线程中修改成员变量并在另一个线程中读取

时间:2018-09-07 09:39:57

标签: c++ pthreads

我有两个成员变量。

class MessageQueues {
    ....
    char* m_InputBuffer;
    uint32_t m_InputBufferSize;
    ....
};

我正在当前线程的一个函数中更新它们,甚至与调试器一起查看成员变量是否已更新。但是在另一个函数中,该函数在单独的线程中运行,成员变量包含垃圾。

所以,我有一个函数,它读取两个值并将它们写入两个成员变量,并在完成后触发另一个线程。

ErrorCode MessageQueue::handleIncomingMessage(char receiveBuffer[], const uint32_t bufferSize) {
    ES_TRC3("started");
    ErrorCode errorCode = ES_SUCCESS;
    pthread_mutex_lock(&mutexMsgQueueIncoming);
    m_InputBuffer = receiveBuffer;
    m_InputBufferSize = bufferSize;
    ES_TRC3("triggering and unlocking");
    pthread_cond_signal(&msgQueueCondition);
    pthread_mutex_unlock(&mutexMsgQueueIncoming);
    ES_TRC3("triggered and unlocked");
    return errorCode;
}

然后加入第二个成员函数,该函数将无限期地在单独的线程中运行并等待消息,

ErrorCode MessageQueue::runReceiver(void) {
    ES_TRC3("started");
    ErrorCode errorCode = ES_SUCCESS;
    while(true)
    {
        ES_TRC3("waiting for input messages");
        pthread_cond_wait(&msgQueueCondition, &mutexMsgQueueIncoming);
        pthread_mutex_lock(&mutexMsgQueueIncoming);
        ES_TRC3("will parse message");
        if (strlen(m_InputBuffer) > 0UL) {
            if ((errorCode = m_MsgProtocol->parseMessage(m_InputBuffer, m_InputBufferSize)) != ES_SUCCESS)
            {
                ES_TRC1("failed to parseMessage, error:%d", errorCode);
            }
        }
        ES_TRC3("message parsed");
        pthread_mutex_unlock(&mutexMsgQueueIncoming);
    }
    return errorCode;
}

借助调试器(以及日志消息),我可以看到变量的更新和触发以正确的顺序发生。因此,我无法弄清楚为什么在第二个函数中,成员变量似乎没有包含正确的值。我在某处读到有关“易失性”的内容。但是不要真的认为,这是正确的答案。我在这里做错什么了吗?

更新: 互斥锁在类之外,但在同一源文件中,像这样在顶部初始化。

pthread_mutex_t mutexMsgQueueIncoming       = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t msgQueueCondition            = PTHREAD_COND_INITIALIZER;

实际上,这很奇怪。当我在没有调试器的情况下运行程序时,似乎互斥体无法在runReceiver()函数中锁定。使用调试器,互斥锁被锁定,但成员变量包含垃圾。好的,我想应该对此进行调查。

1 个答案:

答案 0 :(得分:1)

这里有错误:

  1. 互斥锁被锁定时,必须等待条件变量。 pthread_cond_wait文档是必读的文件。
  2. 条件变量spurious wake-ups必须由while循环处理。

修复:

class MessageQueues {
    ....
    char* m_InputBuffer;
    uint32_t m_InputBufferSize;
    uint32_t m_WriteGeneration = 0; // <--- a fix.
    uint32_t m_ReadGeneration = 0;  // <--- a fix.
    ....
};

ErrorCode MessageQueue::handleIncomingMessage(char receiveBuffer[], const uint32_t bufferSize) {
    ES_TRC3("started");
    ErrorCode errorCode = ES_SUCCESS;
    pthread_mutex_lock(&mutexMsgQueueIncoming);
    m_InputBuffer = receiveBuffer;
    m_InputBufferSize = bufferSize;
    ++m_WriteGeneration; // <--- a fix.
    ES_TRC3("triggering and unlocking");
    pthread_cond_signal(&msgQueueCondition);
    pthread_mutex_unlock(&mutexMsgQueueIncoming);
    ES_TRC3("triggered and unlocked");
    return errorCode;
}

ErrorCode MessageQueue::runReceiver(void) {
    ES_TRC3("started");
    ErrorCode errorCode = ES_SUCCESS;
    while(true)
    {
        ES_TRC3("waiting for input messages");
        pthread_mutex_lock(&mutexMsgQueueIncoming);  // <--- a fix.
        while(m_ReadGeneration == m_WriteGeneration) // <--- a fix.
            pthread_cond_wait(&msgQueueCondition, &mutexMsgQueueIncoming); // <--- a fix.
        m_ReadGeneration = m_WriteGeneration; // <--- a fix.
        ES_TRC3("will parse message");
        if (strlen(m_InputBuffer) > 0UL) {
            if ((errorCode = m_MsgProtocol->parseMessage(m_InputBuffer, m_InputBufferSize)) != ES_SUCCESS)
            {
                ES_TRC1("failed to parseMessage, error:%d", errorCode);
            }
        }
        ES_TRC3("message parsed");
        pthread_mutex_unlock(&mutexMsgQueueIncoming);
    }
    return errorCode;
}

请注意,如果handleIncomingMessage解锁互斥锁时两次调用runReceiver,则此代码将丢失消息。您可能需要在此处放置一个消息队列,而不是存储最后一条消息(即,您有一个大小为1的队列,该队列在溢出时会丢弃旧元素)。

该代码还需要检查pthread函数返回的错误代码。这很繁琐,最好使用C ++ 11 std::mutexstd::condition_variablestd::unique_lock为您做检查。在C ++ 98中,您可以使用boost个等效项。