Лучший способ удалить элемент в списке для атома в Clojure

Я использую server.socket для потоковой передачи данных нескольким клиентам, server.socket использует потоки для каждого клиентского соединения. В настоящее время у меня есть что-то вроде этого:

(def clients (atom ())) ; connected clients defined globally for that namespace

(swap! clients conj a)  ; adds a client (which is an atom itself as well), this is in a function that is run on the client's thread

;I want to better the process of removing a client!
(dosync (reset! clients (remove #{a} @clients))) ; removes client from list, run in a function on the client's thread

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

(doseq [c @clients]
  (print ((deref c) :content))
  (flush))

Я как бы пришел к выводу, что использование атомов в потоках действительно обеспечивает бесперебойную работу программы и позволяет выполнять неблокирующие чтения, поэтому я доволен этим, за исключением того, что я чувствую, что сброс глобального клиента Atom только для того, чтобы я мог удалить один клиент из список - плохой ход. Есть ли более подходящий способ сделать это с помощью swap! ? Я выбрал список для клиентского атома, так как запускаю дозык на каждом подключенном клиенте, чтобы захватить контент и сбросить его в сокет выходного потока.


person scape    schedule 19.07.2013    source источник


Ответы (2)


Избегайте разыменовывания атома внутри swap! или reset!.

Здесь swap! даст вам то, что вам нужно. Он принимает функцию, которая получает текущее значение, которое вы можете использовать для своего обновления:

(def clients (atom '(:a :b :c :d)))
(swap! clients (fn [s] (remove #{:a} s)))

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

(def clients (atom #{:a :b :c :d}))
(swap! clients disj :a)
person A. Webb    schedule 19.07.2013
comment
О, мне это очень нравится, большое спасибо. Я также не хотел ссылаться на клиентов, но не мог придумать другого способа выполнить эту операцию. Спасибо! - person scape; 19.07.2013
comment
Да, большая часть того, что я нашел в Интернете, касалась хэш-карт и почти всего, кроме списков :-\ - person scape; 19.07.2013
comment
(обмен! клиентами (частичное удаление #{:a})) делает его более понятным. - person Leon Grapenthin; 20.07.2013

Конечно, вы можете использовать swap!, см. ответ А. Уэбба.

Возможно, вы захотите подумать, является ли это лучшим выбором для хранения ваших клиентов в списке; набор или карта были бы более естественным выбором (для использования с disj / dissoc). (Если только клиентов всегда очень мало, в этом случае может иметь смысл использовать наименее сложную доступную структуру данных.)

Кроме того, dosync здесь ничего не делает. dosync предназначен для использования с ссылками (и alter, commute, ref-set, ensure).

Я также укажу, что если вы запустите такой цикл:

(doseq [c @clients]
   ...)

тогда он всегда будет перебирать значение clients во время ввода формы doseq, независимо от каких-либо swap!s до clients Atom, которые могли произойти за это время. Не то чтобы это было проблемой, просто нужно иметь в виду.

Еще одна вещь, о которой следует помнить, это то, что ссылочные типы Clojure предназначены для (1) хранения неизменяемых данных, (2) обновления с помощью чистых функций (в swap! / alter / send и других). Размещение атомов в перерывах между атомами (1); это может не быть проблемой здесь, но вы должны убедиться, что это не так. (Нарушение (2) будет проблемой почти всегда, за исключением особых случаев, таких как трассировки отладки, которые вы полностью хотите снова распечатать на сбойном CAS и т. д.)

person Michał Marczyk    schedule 19.07.2013
comment
Спасибо за ваш вклад, я надеялся, что dosync поможет при ссылке на клиентский атом. Я понимаю, что вы говорите, после более внимательного прочтения функции. Я выбрал список, так как я постоянно запускаю его в нескольких потоках, что кажется естественным вместо создания последовательности из вектора или карты. Мне кажется, что я где-то читал, что при использовании дозы для всего, кроме списка, производительность снижается. - person scape; 19.07.2013
comment
На самом деле doseq в векторе особенно быстро из-за фрагментации. На наборе или карте -- может быть, конечно, медленнее, но это не имеет большого значения (ваше dosync тело, несомненно, будет доминировать, если оно вообще хоть как-то работает). И disj из набора будет намного быстрее, чем remove из длинного списка. - person Michał Marczyk; 19.07.2013
comment
Я понимаю, я обнаружил, что с получением «моментального снимка» на этапе дозыq я непрерывно запускаю цикл вокруг дозыq, поэтому каждый раз, когда он запускается, он должен ссылаться на новые значения, что он и делает. - person scape; 19.07.2013
comment
Как атомы в атомах разрушают неизменность? Я ценю ваш вклад, так как я новичок. В основном у меня есть 1 атом, который содержит все клиентские атомы. Основной атом под названием «Клиенты» обновляется только при подключении или отключении нового клиента. Каждый клиент сам обновляет свой собственный атом обновленной информацией. Для меня это имело смысл для того, чего я пытался достичь - поделиться всеми данными со всеми, кто связан. - person scape; 19.07.2013
comment
Атомы @scape сами по себе изменяемы, но их содержимое должно быть неизменным. Атом внутри атома означает, что внешний атом будет иметь изменяемое содержимое, то есть внутренний атом. Это противоречит сути — атомы предназначены для обеспечения контролируемого доступа к состоянию, но в этом случае состояние внешнего атома может быть изменено путем мутации внутреннего атома без консультации с внешним. Вторая проблема заключается в том, что swap! и т. д. могут быть запущены несколько раз за кулисами, поэтому не должны иметь побочных эффектов. Примером побочного эффекта является изменение состояния. Риск очень тонких ошибок. - person A. Webb; 19.07.2013
comment
Поэтому вместо этого вы можете использовать Atom верхнего уровня, содержащий карту идентификатора клиента для данных клиента; каждый клиент затем будет обрабатывать свои собственные данные (возможно, используя swap! update-in [:my-id ...] ...), а добавление/удаление клиентов может быть выполнено с помощью swap! assoc/dissoc. Вам также не нужно предоставлять клиентам доступ к Atom верхнего уровня, чтобы это работало; вместо этого они могут предоставить координатору с соответствующими функциями, которые этот координатор может затем применить к соответствующим фрагментам карты верхнего уровня способом, описанным выше. - person Michał Marczyk; 19.07.2013
comment
По моему опыту, по мере усложнения кода старая добрая эксклюзивная блокировка в целом дает вам гораздо более простую параллельную модель. Можно ломать и ломать голову со всеми возможными взаимодействиями между всеми возможными атомарными обновлениями, а можно ввести всего один простой и банальный замок. - person Marko Topolnik; 20.07.2013