0

我有一个有 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() 中,我想将请求发送到主服务器,如果它失败,我想将它发送到备用服务器,如果它通过,我想交换主服务器和备用服务器。

问题来了:当我进行交换时,我必须锁定主备(这是多个线程不能同时做的地方)。实际上我需要确保当我交换时,没有线程正在读取主服务器。如何以有效的方式编写这段代码?我不想锁定整个事情,因为大多数情况下,主服务器工作并且不需要锁定。

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

4

1 回答 1

0

让我们假设服务器需要一些不可忽略的时间来处理请求。然后,如果请求来得足够快,您将SendRequest遇到第二次调用的情况,而它正在等待其中一个服务器处理先前的请求。

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

  1. 如果服务器可以同时处理多个请求,那么您什么也不做。
  2. 如果服务器一次只能处理一个请求,那么您将需要对代码执行某种同步。

在情况 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();    
  }
}

但这可能会有一些问题。例如,主节点在第一条消息上失败,但在其余消息上成功。如果 SendRequest() 被 3 个不同的线程同时调用,那么您可能会遇到以下情况:

  • 线程 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 将一直使用到它失败为止。然后将使用备份。如果同时处理其他消息,则可能导致主节点再次可用,在这种情况下,它将被使用。否则,将使用备份直到失败。如果两者都失败,它们都将被尝试,首先是主节点,然后是备份节点。

于 2013-08-02T17:14:17.663 回答