Как возможны" неблокирующие " структуры данных?
У меня возникли проблемы с пониманием того, как любая структура данных может быть "неблокирующейся".
скажем, вы делаете" неблокирующую " хэш-таблицу. В какой-то момент ваша хэш-таблица становится слишком полной, поэтому вам нужно повторно хэшировать в большую таблицу.
Это означает, что вы должны выделить память, которая является глобальным ресурсом. Так кажется, что ты должны получите какую-то блокировку, чтобы предотвратить глобальное повреждение кучи... независимо от возможных проблем с вашими данными сама структура!
Но тогда это означает, что каждый поток должен блокировать при выделении памяти...
Что я пропустила?
(Как) вы можете выделить память, не блокируя другой поток, который делает то же самое?
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
- неблокирующая атомарная операция, которая заменяет значение в адресе памяти с новым значением и возвращает старое значение. Если старое значение не соответствует ожидаемому значению, вы прокручиваете цикл и повторяете все это снова.
все это неблокирующий означает, что вы никогда не ждете бесконечно, а не то, что вы никогда не ждете вообще. Пока ваша куча также реализована с использованием неблокирующего алгоритма, вы можете реализовать другие неблокирующие алгоритмы поверх нее.