两个服务器的线程安全类实现

时间:2013-08-02 16:49:09

标签: c++ multithreading architecture thread-safety system

我有一个有2个成员的班级:

RequestController {
  public:
    SendRequest();  // called by multiple threads synchronously
  private:
    server primary;   // This is the primary server
    server backup;    // This is the back up server.
}

我的逻辑就是这样:

在SendRequest()中,我想将请求发送到主服务器, 如果它失败了,我想把它发送到备份服务器,如果它通过, 我想换掉主服务器和备份服务器。

问题出现了:当我进行交换时,我必须锁定主要和 备份(这是多个线程不能同时执行的地方)。 实际上我需要确保在交换时,没有线程正在读取主服务器。 如何以有效的方式编写这段代码? 我不想锁定整个事情,就像大多数人一样 case,主服务器工作,不需要锁定。

我认为这个问题通常与语言无关。无论如何我标记这个 用C ++。

1 个答案:

答案 0 :(得分:0)

让我们假设服务器花费一些不可忽视的时间来处理请求。然后,如果请求的速度足够快,那么在等待其中一个服务器处理先前请求时,将会第二次调用SendRequest

作为设计师,您有两种选择。

  1. 如果服务器可以同时处理多个请求,那么您什么都不做。
  2. 如果服务器一次只能处理一个请求,那么您需要对代码执行某种同步。
  3. 在案例2中,由于您已经锁定了服务器,因此您可以在没有任何后果的情况下进行交换。

    对于案例1,为什么不执行以下操作:

    std::mutex my_mutex;
    ...
    // Select the server
    server* selected = NULL;
    my_mutex.lock();
      selected = &primary;
    my_mutex.unlock();
    
    // Let the selected server process the message.
    bool success = selected->process();
    
    // If there was a primary failure, see if we can try the backup.
    if (!success) {
      my_mutex.lock();
      if (selected == &primary) {
        selected = &backup;
      }
      my_mutex.unlock();
    
      // Now try again
      success = selected->process();
    
      // If the backup was used successfully, swap the primary and backup.
      if (success) {
        my_mutex.lock();
        if (selected == &backup) {
          backup = primary;
          primary = selected;
        }
        my_mutex.unlock();    
      }
    }
    

    但这可能会有一些问题。比如说主要在第一条消息上失败,但在其余消息上成功失败。如果通过3个不同的线程同时调用SendRequest(),那么您可以拥有以下内容:

    • 主题1 - 主要发送
    • 主题2 - 主要发送
    • 主题3 - 主要发送
    • 线程1 - 失败,带备份发送
    • 主题2 - 主要成功
    • 主题1 - 备份成功
    • 主题1 - 交换主要和备份
    • 主题3 - 旧主要(新备份)成功
    • 主题3 - 交换主要和备份

    如果邮件的速度足够快,则可以保持这种状态,即您可以继续交换主邮件和备份邮件。条件将解决没有待处理消息的时刻,然后将设置主要和备份,直到出现另一个故障。

    也许更好的方法是永不交换,但有更好的选择方法。例如:

    ...
    // Select the server
    server* selected = NULL;
    selected = &primary;
    if (!primary.last_message_successful) {
      // The most recent attempt made with primary was a failure.
      if (backup.last_message_successful) {
        // The backup is thought to be functioning.
        selected = &backup;
      }
    }
    
    // Let the selected server process the message.
    // If successful, process() will set the last_message_successful boolean.
    bool success = selected->process();
    
    // If there was a failure, try the other one.
    if (!success) {
      if (&primary == selected) {
        selected = &backup;
      } else {
        selected = &primary;
      }
    }
    
    // Try again with the other one.
    selected->process();
    

    在此示例中,不需要锁定。将使用Primary,直到失败。然后将使用备份。如果同时处理其他消息,则可能导致主要消息再次变为可用,在这种情况下将使用它。否则,将使用备份直到失败。如果两者都失败,则会尝试它们,首先是主要的,然后是备份。