Подтвердить что ты не робот

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

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

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


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


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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

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

  • старое значение берется из атома и хранится в потоковом локальном
  • функция применяется к старому значению для генерации нового значения
  • если это удается, метод compare-and-set вызывается со старым и новым значением; только если значение атома не было изменено никаким другим потоком (все еще равным старому значению), новое значение записывается, в противном случае операция перезапускается в (1), пока это не удастся в конечном итоге.

Ответ 3

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

Вы говорите:

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

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 не имеет к этому никакого отношения, но, возможно, я неправильно понимаю вашу мысль.

Ответ 4

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

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

(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

Ответ 5

У Vars не всегда есть привязка корня. Это право создавать var без привязки с помощью

(def x)

или

(declare x)

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

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