Изменяемые типы хранилищ Clojure
-
06-07-2019 - |
Вопрос
Я пытаюсь изучить Clojure, используя API и документацию, доступную на сайте.Мне немного неясно, что такое изменяемое хранилище в Clojure, и я хочу убедиться, что мое понимание правильное.Пожалуйста, дайте мне знать, если есть какие-либо идеи, в которых я ошибся.
Редактировать:Я обновляю это по мере получения комментариев о его правильности.
Отказ от ответственности:Вся эта информация неофициальна и потенциально неверна.Не используйте этот пост для понимания того, как работает Clojure.
Варс всегда содержат корневую привязку и, возможно, привязку для каждого потока.Они сравнимы с обычными переменными в императивных языках и не подходят для обмена информацией между потоками. (спасибо Артуру Ульфельдту)
Ссылки — это местоположения, совместно используемые потоками, которые поддерживают атомарные транзакции, которые могут изменить состояние любого количества ссылок в одной транзакции.Транзакции фиксируются при выходе из выражений синхронизации (dosync), а конфликты разрешаются автоматически с помощью магии STM (откаты, очереди, ожидания и т. д.).
Агенты Это местоположения, которые позволяют асинхронно распределять информацию между потоками с минимальными издержками путем отправки независимых функций действий для изменения состояния агента.Агенты возвращаются немедленно и поэтому не блокируются, хотя значение агента не устанавливается до завершения отправленной функции.
Атомы — это местоположения, которые могут синхронно совместно использоваться потоками.Они поддерживают безопасное манипулирование между различными потоками.
Вот мое краткое изложение, основанное на том, когда использовать эти структуры:
- Вары похожи на обычные старые переменные в императивных языках.(избегайте, когда это возможно)
- Атомы похожи на Vars, но с безопасностью совместного использования потоков, которая обеспечивает немедленное чтение и безопасную настройку. (спасибо Мартин)
- Агент похож на атом, но вместо того, чтобы блокировать его, он создает новый поток для вычисления его значения, блокируется только в середине изменения значения и может сообщить другим потокам, что он завершил присвоение.
- Ссылки — это общие местоположения, которые блокируются в транзакциях.Вместо того, чтобы заставлять программиста решать, что происходит во время гонок для каждого фрагмента заблокированного кода, мы просто запускаем транзакцию и позволяем Clojure обрабатывать все условия блокировки между ссылками в этой транзакции.
Также родственным понятием является функция future
.Мне кажется, что будущий объект можно описать как синхронный агент, к значению которого невозможно получить доступ вообще, пока вычисление не будет завершено.Его также можно назвать неблокирующим атомом.Это точные представления о будущем?
Решение
Похоже, вы действительно получаете Clojure!хорошая работа :)
У переменных есть «корневая привязка», видимая во всех потоках, и каждый отдельный поток может изменить значение, которое он видит, не затрагивая другие потоки.Если я правильно понимаю, переменная не может существовать только в одном потоке без корневой привязки, видимой для всех, и не может быть «восстановлена», пока не будет определена с помощью (def ...) первый раз.
Ссылки фиксируются в конце (dosync...) транзакция, которая включает в себя изменения, но только тогда, когда транзакция смогла завершиться в согласованном состоянии.
Другие советы
Я думаю, что ваш вывод об атомах неверен:
Атомы похожи на Vars, но с безопасностью совместного использования потоков, которая блокируется до тех пор, пока значение не изменится.
Атомы изменяются с swap!
или низкого уровня с compare-and-set!
.Это никогда ничего не блокирует. swap!
работает как транзакция только с одной ссылкой:
- старое значение берется из атома и сохраняется локально в потоке
- функция применяется к старому значению для создания нового значения
- если это удается, вызывается функция сравнения и установки со старым и новым значением;только если значение атома не было изменено каким-либо другим потоком (все еще равно старому значению), записывается новое значение, в противном случае операция перезапускается с (1) до тех пор, пока в конечном итоге не завершится успешно.
Я обнаружил две проблемы в вашем вопросе.
Ты говоришь:
Если к агенту обращаются во время выполнения действия, значение не возвращается до тех пор, пока действие не завершится.
http://clojure.org/agents говорит:
состояние Агента всегда доступно для чтения любому потоку.
Т.е.вам никогда не придется ждать, чтобы получить значение агента (я предполагаю, что значение, измененное действием, проксируется и изменяется атомарно).
Код для deref
-метод Agent
выглядит так (версия SVN 1382):
public Object deref() throws Exception{
if(errors != null)
{
throw new Exception("Agent has errors", (Exception) RT.first(errors));
}
return state;
}
Никакой блокировки не происходит.
Кроме того, я не понимаю, что вы имеете в виду (в разделе «Ссылка») под
Транзакции фиксируются при вызове deref
Транзакции фиксируются, когда все действия блока dosync завершены, не было создано никаких исключений и ничто не привело к повторной попытке транзакции.Я думаю deref
не имеет к этому никакого отношения, но, возможно, я неправильно понял вашу точку зрения.
Мартин прав, когда говорит, что работа Atoms возобновляется с 1.до тех пор, пока в конечном итоге это не удастся.Это также называется ожиданием вращения.Хотя на самом деле блокируется блокировка, поток, выполнивший операцию, блокируется до тех пор, пока операция не завершится успешно, поэтому это блокирующая операция, а не асинхронная операция.
Что касается фьючерсов, в Clojure 1.1 добавлены абстракции для обещаний и фьючерсов.Промис — это конструкция синхронизации, которую можно использовать для доставки значения из одного потока в другой.Пока значение не будет доставлено, любая попытка разыменовать обещание будет заблокирована.
(def a-promise (promise))
(deliver a-promise :fred)
Фьючерсы представляют собой асинхронные вычисления.Это способ запустить код в другом потоке и получить результат.
(def f (future (some-sexp)))
(deref f) ; blocks the thread that derefs f until value is available
Вары не всегда имеют корневую привязку.Разрешено создавать переменную без привязки, используя
(def x)
или
(declare x)
Попытка вычислить x до того, как он примет значение, приведет к
Var user/x is unbound.
[Thrown class java.lang.IllegalStateException]