Как возможны" неблокирующие " структуры данных?

У меня возникли проблемы с пониманием того, как любая структура данных может быть "неблокирующейся".

скажем, вы делаете" неблокирующую " хэш-таблицу. В какой-то момент ваша хэш-таблица становится слишком полной, поэтому вам нужно повторно хэшировать в большую таблицу.

Это означает, что вы должны выделить память, которая является глобальным ресурсом. Так кажется, что ты должны получите какую-то блокировку, чтобы предотвратить глобальное повреждение кучи... независимо от возможных проблем с вашими данными сама структура!
Но тогда это означает, что каждый поток должен блокировать при выделении памяти...

Что я пропустила?
(Как) вы можете выделить память, не блокируя другой поток, который делает то же самое?

4 ответов


два примера для неблокирующих конструкций являются оптимистичный дизайн и Транзакций.

идея этого - в большинстве случаев блокировка избыточна, поскольку два OPs могут одновременно происходить без прерывания друг друга. Однако иногда, когда 2 операции происходят одновременно, и данные повреждаются из - за этого-вы можете откатиться к предыдущему состоянию и повторить попытку.

в этих конструкциях все еще могут быть замки, но ... --9-->времени данные заблокированы значительно короче и ограничены только критическим временем, когда происходит влияние OP.


просто для некоторых определений, дополнительной информации и различать неблокирующий, lock-free и ожидания условия, я рекомендую прочитать следующую статью (я не буду копировать соответствующие отрывки здесь, так как это слишком долго):

определения неблокирующих, без блокировки и без ожидания


большинство стратегий имеют одну общую фундаментальную закономерность. Они используют сравнить и поменять местами (CAS) операции в цикле, пока не удается.

например, рассмотрим стек, реализованный со связанным списком. Я выбрал реализацию связанного списка, потому что ее легко сделать параллельной CAS, но есть и другие способы сделать это. Я буду использовать C-подобный псевдокод.

Push(T item)
{
  Node node = new Node(); // allocate node memory
  Node initial;
  do
  {
    initial = head;
    node.Value = item;
    node.Next = initial;
  }
  while (CompareAndSwap(head, node, initial) != initial);
}

Pop()
{
  Node node;
  Node initial;
  do
  {
    initial = head;
    node = initial.Next;
  }
  while (CompareAndSwap(head, node, initial) != initial);
  T value = initial.Value;
  delete initial; // deallocate node memory
  return value;
}

В приведенном выше коде CompareAndSwap - неблокирующая атомарная операция, которая заменяет значение в адресе памяти с новым значением и возвращает старое значение. Если старое значение не соответствует ожидаемому значению, вы прокручиваете цикл и повторяете все это снова.


все это неблокирующий означает, что вы никогда не ждете бесконечно, а не то, что вы никогда не ждете вообще. Пока ваша куча также реализована с использованием неблокирующего алгоритма, вы можете реализовать другие неблокирующие алгоритмы поверх нее.