Шаблон блокировки кэша с C ++ - PullRequest
2 голосов
/ 15 октября 2011

Мне интересно, есть ли лучшая схема блокировки для кэша, чем простая блокировка:

Mutex lock;
get(key) {
  LockGuard(lock);

  if (cache.has(key)) {
    return cache[key];
  } else {
    data = remoteclient.getslow();
    cache[key] = data;
    return data;
  }
}

Если у вас много одинаковых запросов, вы каждый раз сериализуете доступ к get (). Можно ли сделать что-нибудь более умное с помощью блокировок ReadWriter?

т.е. Что делать, если вы делаете что-то вроде:

ReadersWritersLock lock;
get(key) {
  {
    ReadLockGuard(lock);
    if (cache.has(key)) {
      return cache[key];
    } 
  }
  WriteLockGuard(lock);
  data = remoteclient.getslow();
  cache[key] = data;
  return data;
 }
}

Теперь это позволит нескольким пользователям одновременно получать () в случае попадания в кеш. Однако, если два пользователя попадают в первый метод get () примерно в одно и то же время, возможно, они оба попытаются войти во вторую часть кода для получения данных. Это кажется хорошей идеей?

Есть еще идеи по оптимизации такого рода кода?

Ответы [ 3 ]

2 голосов
/ 15 октября 2011

Одна вещь, которая мне не нравится в размещенном коде, это то, что в обоих фрагментах вызов

remoteclient.getslow();

вызывается, пока кеш заблокирован. Если на самом деле remoteclient.getslow (), вероятно, займет много времени для возврата (как видно из названия), то любые другие потоки, пытающиеся получить доступ к кешу, в конечном итоге будут заблокированы на долгое время (т. Е. До тех пор, пока getslow () не вернется и вызывающий поток снимает блокировку) ... даже если они заинтересованы только в несвязанных данных, которые уже присутствуют в кэше!

Чтобы избежать этого, я бы вместо этого вызывал remoteclient.getslow () вне области действия LockGuard (то есть, когда кеш разблокирован). Затем, после того, как remoteclient.getslow () вернет результат, я снова заблокирую кеш и обновлю кеш полученным значением. Таким образом, кеш никогда не блокируется на длительные периоды.

(Конечно, выполнение этого способа открывает возможность для нескольких потоков вызывать remoteclient.getslow () для одного и того же элемента данных, если они все решат, что им нужны одни и те же данные примерно в одно и то же время ... но это может быть приемлемым побочным эффектом. Или, если нет, вы можете разработать механизм для указания того, что определенное значение кэша находится в процессе извлечения, и чтобы другие потоки блокировались до тех пор, пока поиск не завершится ... если это стоит дополнительной сложности для вас. Это, вероятно, потребует условных переменных и тому подобное, чтобы сделать правильно)

1 голос
/ 15 октября 2011

Возможно, что два потока войдут в «запись» части get (), но, вероятно, очень маловероятно.Если вас беспокоит штраф за дополнительный вызов getslow (), вы можете проверить еще раз внутри блокировки писателя.

ReadersWritersLock lock;
get(key) {
  {
    ReadLockGuard(lock);
    if (cache.has(key)) {
      return cache[key];
    } 
  }
  WriteLockGuard(lock);
  if (cache.has(key) == false) {
    data = remoteclient.getslow();
    cache[key] = data;
    return data;
  }
 }
1 голос
/ 15 октября 2011

Ваш псевдокод имеет правильную идею, но имеет условие гонки.

Как только ReadLockGuard выходит из области видимости, вы теряете блокировку, что означает, что структура данных может быть изменена другим потоком до того, как WriteLockGuard успеет захватить блокировку.

Если ваша реализация блокировки чтения / записи поддерживает обновляемые блокировки, то вам следует использовать это. В противном случае, после захвата блокировки для записи, вам необходимо перепроверить кэш на случай, если он будет заполнен между выпуском «reader» и получением «writer».

...