CPL 2016, week 14 Clojure agents Oleg Batrashev Institute of Computer Science, Tartu, Estonia May 9, 2016
Overview Last weeks ◮ Clojure language core ◮ Immutable data structures ◮ Clojure simple state and design ◮ Software transactional memory (STM) This week ◮ Agents in Clojure
Clojure agents 81/90 - Scope ◮ Agent programming is a term used in many contexts ◮ concurrent entities ◮ intelligent agents ◮ agents with goals ◮ cooperating agents ◮ etc We are primarly interested in properties arising from concurrency.
Clojure agents 82/90 - General ◮ Agent in Clojure is a reference type (mutable holding state) that can be changed by sending it an action (function) ◮ uncoordinated – independent of changes of other agents ◮ asynchronous – changes are away from the thread that schedules an action ◮ immediate return: no wait for the completion of an action ◮ 2 characteristics of Clojure agents ◮ I/O may be used safely with agents unlike in STM ◮ agents are STM aware
Clojure agents 83/90 - Agent mechanics ◮ reading agent’s state deref / @ – non-blocking ◮ (send a action-fn & args) – send agent a an action ◮ action-fn takes agent state, args and returns new agent state ◮ as with (apply action-fn state-of-agent args) ◮ actions are stored into the queue for each agent ◮ processed sequentially by threads, mutating its state ◮ thread from one of two thread pools ◮ thread pools ◮ fixed size – actions delivered from send ◮ unlimited size – actions delivered from send-off ◮ needed for blocking IO
Clojure agents 84/90 - Agent example ◮ agent with the state of single integer – counter ◮ await is only needed to show the result, in general agents must be asynchronous (def counterAgent (agent 0)) (send counterAgent inc) (send counterAgent (fn [state] (+ state 3))) (send counterAgent (fn [state msg] (+ state msg )) 10) (await counterAgent ) (println @counterAgent )
Clojure agents 85/90 - Diagram ◮ black actions are CPU bound (send) ◮ threads t 2 and t 3 are from fixed size pool ◮ threads t 18 and t 9 are from unbounded pool ◮ for single agent all actions still are sequential, although may be running in different threads ◮ *agent* is the agent currently run by the thread
Clojure agents 86/90 - Semantics of agents ◮ agents != threads ◮ usually number of threads << number of agents ◮ agents still run concurrently -> green threads ◮ not as in typical Remote Procedure Call (RPC), where ◮ allow to enter multiple threads instead of one ◮ state is not declarative ◮ reduce – value from a list + operation = action (fun + args) ◮ intermediate reduction values = states ◮ await / await-for – wait for all actions from current thread to complete
Clojure agents 87/90 - Errors in agents ◮ agent on exception goes to error state ◮ agent-error returns exception or nil ◮ sending actions to agent throw the exception ◮ (restart-agent agent new-state & options) – clears error state ◮ :clear-actions also clears the action queue ◮ when creating an agent ◮ :error-mode , either :fail (default) or :continue ◮ agent is not failed on ’continue’ mode ◮ :error-handler – provide function that is called in ’continue’ mode ◮ set-error-mode!, set-error-handler!
Clojure agents 88/90 - IO/STM/nested sends ◮ synchronization point for IO ◮ exclusive access to file,socket,stream ◮ others schedule actions on this agent ◮ unlike refs/atoms that should avoid side-effects because of possible transaction restarts ◮ STM aware: any actions sent within a transaction are held until it commits ◮ nested send: any actions sent within an agent are withheld until current action is finished
Clojure agents 89/90 - Watches ◮ watch the reference (atom, ref, or agent) ◮ watch – function that is called when the reference is changed (defn watchFun [key identity old new] (println identity "changed value from " old "to" new )) (def counterAgent (agent 0)) (add -watch counterAgent :mywatch watchFun) (send counterAgent inc) (send counterAgent (fn [state] (+ state 3))) (send counterAgent (fn [state msg] (+ state msg )) 10) ◮ identity is atom, ref, or agent ◮ key allows to attach several watch functions and detach them ◮ identity may already have different value, when watch function is called
Clojure agents 90/90 - Example: log the game ◮ Add watchers to game atoms, refs ◮ watchers send states to logging agent(s) ◮ watcher callback is called in the transaction thread, presumably before releasing the ref ◮ correct order of ref states are queued to the agent(s) ◮ logging game changes and not states requires interception of game methods ◮ must be done (!) in the transaction because of possible transaction restarts ◮ log action is delayed until commit
Recommend
More recommend