Clojure - Параллельное программирование

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

Программная система транзакционной памяти (STM), доступная через dosync, ref, set, alter и т. Д., Поддерживает совместное использование изменяющегося состояния между потоками синхронным и скоординированным образом. Система агентов поддерживает совместное использование изменяющегося состояния между потоками асинхронно и независимо. Система атомов поддерживает совместное использование изменяющегося состояния между потоками синхронно и независимо. В то время как система динамических переменных, предоставляемая через def, binding и т. Д., Поддерживает изоляцию изменяющегося состояния внутри потоков.

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

  • У них есть прямая ссылка на данные, которые можно изменить.

  • Если требуется общий доступ, объект блокируется, значение изменяется, и процесс продолжается для следующего доступа к этому значению.

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

В Clojure есть три типа ссылок.

  • Vars - Изменения изолированы в потоках.

  • Refs - Изменения синхронизируются и координируются между потоками.

  • Agents - Включает асинхронные независимые изменения между потоками.

В Clojure возможны следующие операции в отношении параллельного программирования.

Сделки

Параллелизм в Clojure основан на транзакциях. Ссылки можно изменить только в рамках транзакции. В сделках применяются следующие правила.

  • Все изменения атомарны и изолированы.
  • Каждое изменение ссылки происходит в транзакции.
  • Никакая транзакция не видит эффекта от другой транзакции.
  • Все транзакции размещаются внутри блока dosync.

Мы уже видели, что делает блок dosync, давайте посмотрим на это еще раз.

досинхронизация

Выполняет выражение (в неявном do) в транзакции, которая включает выражение и любые вложенные вызовы. Запускает транзакцию, если в этом потоке еще не запущена. Любое неперехваченное исключение прервет транзакцию и выйдет из режима dosync.

Ниже приводится синтаксис.

Синтаксис

(dosync expression)

Parameters - «выражение» - это набор выражений, которые войдут в блок dosync.

Return Value - Нет.

Давайте посмотрим на пример, в котором мы пытаемся изменить значение ссылочной переменной.

пример

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (def names (ref []))
   (alter names conj "Mark"))
(Example)

Выход

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

Caused by: java.lang.IllegalStateException: No transaction running
   at clojure.lang.LockingTransaction.getEx(LockingTransaction.java:208)
   at clojure.lang.Ref.alter(Ref.java:173)
   at clojure.core$alter.doInvoke(core.clj:1866)
   at clojure.lang.RestFn.invoke(RestFn.java:443)
   at clojure.examples.example$Example.invoke(main.clj:5)
   at clojure.examples.example$eval8.invoke(main.clj:7)
   at clojure.lang.Compiler.eval(Compiler.java:5424)
   ... 12 more

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

Чтобы приведенный выше код работал, мы должны поместить команду alter в блок dosync, как это сделано в следующей программе.

пример

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (def names (ref []))
   
   (defn change [newname]
      (dosync
         (alter names conj newname)))
   (change "John")
   (change "Mark")
   (println @names))
(Example)

Вышеупомянутая программа производит следующий вывод.

Выход

[John Mark]

Давайте посмотрим еще один пример dosync.

пример

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (def var1 (ref 10))
   (def var2 (ref 20))
   (println @var1 @var2)
   
   (defn change-value [var1 var2 newvalue]
      (dosync
         (alter var1 - newvalue)
         (alter var2 + newvalue)))
   (change-value var1 var2 20)
   (println @var1 @var2))
(Example)

В приведенном выше примере у нас есть два значения, которые изменяются в блоке dosync. Если транзакция прошла успешно, оба значения изменятся, иначе транзакция завершится неудачей.

Вышеупомянутая программа производит следующий вывод.

Выход

10 20
-10 40