Типы изменяемых хранилищ Clojure

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

Изменить: я обновляю это, когда получаю комментарии о его правильности.


Отказ от ответственности: вся эта информация является неофициальной и потенциально неверной. Не используйте этот пост для понимания того, как работает Clojure.


Vars всегда содержат корневую привязку и, возможно, привязку для каждого потока. Они сопоставимы с обычными переменными в императивных языках и не подходят для обмена информацией между потоками. (спасибо Артуру Ульфельдту)

Refs — это местоположения, совместно используемые потоками, которые поддерживают атомарные транзакции, которые могут изменять состояние любого количества ссылок в одной транзакции. Транзакции фиксируются при выходе из выражений синхронизации (dosync), а конфликты разрешаются автоматически с помощью магии STM (откаты, очереди, ожидания и т. д.).

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

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

Вот мое дружеское резюме, основанное на том, когда использовать эти структуры:

  • Переменные похожи на обычные старые переменные в императивных языках. (избегайте, когда это возможно)
  • Атомы похожи на Vars, но с безопасностью совместного использования потоков, что позволяет немедленно читать и безопасно устанавливать. (спасибо, Мартин)
  • Агент подобен атому, но вместо того, чтобы блокировать, он порождает новый поток для вычисления его значения, блокируется только в середине изменения значения и может сообщить другим потокам, что он закончил назначение.
  • Рефы — это общие местоположения, которые блокируют себя в транзакциях. Вместо того, чтобы заставлять программиста решать, что происходит во время условий гонки для каждого фрагмента заблокированного кода, мы просто запускаем транзакцию и позволяем Clojure обрабатывать все условия блокировки между ссылками в этой транзакции.

Кроме того, родственным понятием является функция future. Мне кажется, что будущий объект можно описать как синхронный агент, значение которого вообще невозможно получить, пока не будет завершено вычисление. Его также можно описать как неблокирующий атом. Являются ли это точными представлениями о будущем?


person Kai    schedule 22.06.2009    source источник


Ответы (5)


Похоже, вы действительно получаете Clojure! Молодец :)

У переменных есть «корневая привязка», видимая во всех потоках, и каждый отдельный поток может изменить значение, которое он видит, не затрагивая другие потоки. Если я правильно понимаю, var не может существовать только в одном потоке без корневой привязки, которая видна всем, и она не может быть «восстановлена», пока она не будет определена с помощью (def...) в первый раз.

Рефы фиксируются в конце транзакции (dosync...), которая заключает в себе изменения, но только тогда, когда транзакция смогла завершиться в согласованном состоянии.

person Arthur Ulfeldt    schedule 22.06.2009
comment
Вы очень четко объясняете эти исправления, спасибо! Идея корневой привязки меня немного смущает, потому что я не вижу в ней никаких преимуществ. Я предполагаю, что это связано скорее с технической причиной, например, с хранением изменяемых переменных в стеке, чтобы они не освобождались. - person Kai; 22.06.2009

Я думаю, что ваш вывод об атомах неверен:

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

Атомы изменяются с помощью swap! или низкоуровневые с помощью compare-and-set!. Это никогда ничего не блокирует. swap! работает как транзакция только с одной ссылкой:

  1. старое значение берется из атома и сохраняется локально в потоке
  2. функция применяется к старому значению для генерации нового значения
  3. если это удается, вызывается функция сравнения и установки со старым и новым значением; только если значение атома не было изменено каким-либо другим потоком (по-прежнему равно старому значению), записывается новое значение, в противном случае операция перезапускается с (1) до тех пор, пока в конечном итоге не завершится успешно.
person Community    schedule 24.06.2009
comment
Ах, я думаю, ты прав, блокировки нет. В таком случае мне интересно, что имеется в виду, когда говорят, что атомы синхронны. - person Kai; 24.06.2009
comment
свопы не совсем похожи на транзакции в том смысле, что они не объединяются с другими транзакциями — будьте осторожны, если вы используете своп! внутри транзакции, поскольку она небезопасна для транзакций, в частности, она может повторяться несколько раз или мешать другой выполняющейся транзакции. - person mikera; 18.02.2012

Я нашел две проблемы с вашим вопросом.

Ты говоришь:

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

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;

}

Никакой блокировки не происходит.

Кроме того, я не понимаю, что вы имеете в виду (в разделе Ref) под

Транзакции фиксируются при вызовах deref

Транзакции фиксируются, когда все действия блока dosync завершены, исключений не возникло и ничто не привело к повторной попытке транзакции. Я думаю, что deref не имеет к этому никакого отношения, но, возможно, я неправильно понял вашу мысль.

person pmf    schedule 22.06.2009
comment
Привет, pmf, я чувствую, что ты смущен агентами. состояние агента всегда доступно немедленно, но его значение асинхронно изменяется Clojure, когда функция действия завершается после того, как вы вызываете send. В своем объяснении я имел в виду только последний шаг диспетчеризации Агента, а именно: 5. Если во время выполнения функции выполняются какие-либо другие диспетчеризации (прямо или косвенно), они будут удерживаться до тех пор, пока состояние Агента не будет изменено. . Это единственная блокировка, происходящая здесь. - person Kai; 23.06.2009
comment
Мое утверждение о совершении транзакций взято из памяти на основе слайдов, которые я прочитал у Рича Хикли, хотя, возможно, я ошибаюсь. Для меня также имеет смысл коммитить после dosync. - person Kai; 23.06.2009

Мартин прав, когда говорит, что операция Atom перезапускается с 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
person skyde    schedule 22.02.2010

Вары не всегда имеют корневую привязку. Законно создавать var без привязки, используя

(def x)

or

(declare x)

Попытка оценить x до того, как он получит значение, приведет к

Var user/x is unbound.
[Thrown class java.lang.IllegalStateException]
person Allen Rohner    schedule 18.09.2010