Clojure изменяемые типы хранения - PullRequest
27 голосов
/ 22 июня 2009

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

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


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


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

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

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

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

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

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

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

Ответы [ 5 ]

5 голосов
/ 22 июня 2009

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

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

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

4 голосов
/ 24 июня 2009

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

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

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

  1. старое значение берется из атома и сохраняется в локальном потоке
  2. функция применяется к старому значению для генерации нового значения
  3. если это успешно, сравниваются и устанавливаются со старым и новым значением; только если значение атома не было изменено каким-либо другим потоком (по-прежнему равно старому значению), новое значение записывается, в противном случае операция перезапускается с (1) до тех пор, пока в конце концов не завершится успешно.
3 голосов
/ 23 июня 2009

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

Вы говорите:

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

http://clojure.org/agents говорит:

Состояние Агента всегда сразу доступно для чтения любым потоком

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

Код deref -метода Agent выглядит следующим образом (SVN revision 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 не имеет к этому никакого отношения, но, возможно, я неправильно понимаю вашу точку зрения.

1 голос
/ 23 февраля 2010

Мартин прав, когда говорит, что операция 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
0 голосов
/ 19 сентября 2010

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

(def x)

или

(declare x)

Попытка вычислить x, прежде чем он будет иметь значение, приведет к

Var user/x is unbound.
[Thrown class java.lang.IllegalStateException]
...