Skip to content

Instantly share code, notes, and snippets.

@andfadeev
Created April 18, 2024 10:14
Show Gist options
  • Save andfadeev/35c351682c2df7d168c0473e4b96323e to your computer and use it in GitHub Desktop.
Save andfadeev/35c351682c2df7d168c0473e4b96323e to your computer and use it in GitHub Desktop.
(ns concurrency
(:require [clj-http.client :as client]
[clojure.tools.logging :as log]
[com.climate.claypoole :as cp]))
;; we won't touch STM (software transactional memory), agents, refs etc
;; let's focus on things that are actually used a lot in real applications
;; first of all we have immutable DS in clojure and just that removed a huge slice of all
;; concurrency related problems
;; if we pass a map to some library function or some code that we don't know
;; we don't event worry that original map that we pass will be changed - IT's Immutable!
(defn some-scary-black-box-fn
[my-object])
(comment
(let [my-object {:name "value"}]
(some-scary-black-box-fn my-object)
my-object))
;; so why we even need to have parallel threads - to make your application faster
;; and utilize resources better, if we have to spin 10 independent requests (and combine results later)
;; taking each 5 seconds execute one-by-one is 50 seconds, but all in parallel is just 5sec (maybe a bit more)
;; execute something in a thread
(defn run-java-thread
[]
(log/info "Before thread")
(.start (new Thread (fn []
(Thread/sleep 1000)
(log/info "from thread"))))
(log/info "After thread"))
;; but that's too much code + java interop
;; clojure Future instead
(defn run-clojure-future
[]
(log/info "Before future")
(let [f
(future
(Thread/sleep 1000)
(log/info "From future body")
(log/info "from future")
{:result "from future"})]
(log/info "After future")
(log/info "Result of future is " (deref f)))
)
(defn parallel-requests
[]
(let [urls (->> (client/get "https://pokeapi.co/api/v2/pokemon-species?limit=100"
{:as :json})
:body
:results
(map :url))]
(->> urls
(map (fn [url]
(future
(-> (client/get url {:as :json})
:body
(select-keys [:name :shape])))))
(map deref)
(doall))))
;; Atoms
(def counter 0)
(defn inc-counter-test
[]
(alter-var-root #'counter (constantly 0))
(let [f1 (future
(dotimes [_ 1000]
;(Thread/sleep 1)
(alter-var-root #'counter (constantly (inc counter))))
(log/info "f1 ends")
::f1
)
f2 (future
(dotimes [_ 1000]
;(Thread/sleep 2)
(alter-var-root #'counter (constantly (inc counter))))
(log/info "f2 ends")
::f2)]
(and (deref f1) (deref f2))
(log/info "Here!" @f1 @f2)
counter
)
)
(def counter-atom (atom 0))
(defn inc-counter-atom-test
[]
(reset! counter-atom 0)
(let [f1 (future
(dotimes [_ 1000]
(swap! counter-atom inc))
::f1)
f2 (future
(dotimes [_ 1000]
(swap! counter-atom inc))
::f2)]
(log/info "Here!" @f1 @f2)
(deref counter-atom)))
(swap! counter-atom (fn [old-value arg1]
(println old-value arg1)
old-value
) 1)
;; be careful with pmap - it's potentially will not give you the boost as you expect
;; so when you learn clojure there map but also pmap, and looks like people just assume that it's a magic fn to make
;; the processing faster, it's actually try but only for cpu intensive tasks
(comment (pmap (fn [input]) [1 2 3]))
;; futures are easy and great, but what if you want more control
;; so futures are execute on an unbounded thread pool, so it's hard to control how much is running at max
;; we have claypoole library to the resque
(defn claypoole-pmap-example
[]
(let [urls []
responses (cp/pmap (cp/threadpool 100)
(fn [url]
(client/get url)) urls)]
(doall responses)))
(defn claypoole-parallel-requests
[]
(let [urls (->> (client/get "https://pokeapi.co/api/v2/pokemon-species?limit=100"
{:as :json})
:body
:results
(map :url))]
(->> urls
(cp/pmap
(cp/threadpool 100)
(fn [url]
(-> (client/get url {:as :json})
:body
(select-keys [:name :shape]))))
(doall))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment