2. 목 차
• 원자(Atom)
• Agent(에이전트)
• STM(Software Transaction Memory)
• 원자와 STM의 비교
3. 순수 함수형 언어는 가변 데이터를 지원하지 않는다
Clojure는 동시성을 염두에둔 가변 데이터를 지원
(원자, 에이전트, STM)
4. 원자
user=> (def my-atom (atom 42)) ; 원자 정의
#’user/my-atom
user=> (deref my-atom) ; 값 조회
42
user=> @my-atom ; 값 조회
42
user=> (swap! my-atom inc) ; 원자에 inc 함수 적용
43
user=> (swap! my-atom + 2) ; 원자에 + 함수 적용
45
user=> (reset! my-atom 0) ; 원자의 값을 변경
0
5. 예제: 멀티스레딩
(def players (atom ())) ; 원자 players 정의
(defn list-players []
(response (json/encode @players))) ; players를 조회
(defn create-player [player-name]
(swap! players conj player-name) ; 새로운 player 추가
(status (response "") 201))
(defroutes app-routes
(GET "/players" [] (list-players))
(PUT "/players/:player-name" [player-name] (create-player player-name)))
(defn -main [& args]
(run-jetty (site app-routes) {:port 3000}))
players를 json으로 변환하는중
새로운 player를 추가된다면?
클로저의 자료구조는 영속적 속성을 가지므로 스레드에 안전
6. 클로저의 영속성 자료구조
• 여기서 영속성은 불변성과 유사한 의미
• 값이 변경 되었을때 이전 값을 유지
user=> (def listv1 (list 1 2 3))
#’user/listv1
user=> listv1
(1 2 3)
; 새로운 항목 추가
user=> (def listv2 (cons 4 listv1))
#’user/listv2
user=> listv2
(4 1 2 3)
{:age 45, :name "paul"}
user=> mapv2
{:age 45, :name "paul", :sex :male}
Persistent data structures behave as though a complete copy is made each
time they’re modified. If that were how they were actually implemented, they
would be very inefficient and therefore of limited use (like CopyOnWriteArrayList,
which we saw in Copy on Write, on page 34). Happily, the implementation is
much more clever than that and makes use of structure sharing.
The easiest persistent data structure to understand is the list. Here’s a simple
list:
user=> (def listv1 (list 1 2 3))
#'user/listv1
user=> listv1
(1 2 3)
And here’s a diagram of what it looks like in memory:
1 2 3
listv1
Now let’s create a modified version with cons, which returns a copy of the list
with an item added to the front:
user=> (def listv2 (cons 4 listv1))
#'user/listv2
user=> listv2
(4 1 2 3)
The new list can share all of the previous list—no copying necessary:
1 2 3
listv1
4
listv2
Finally, let’s create another modified version:
Persistent data structures behave as though a complete copy is made each
time they’re modified. If that were how they were actually implemented, they
would be very inefficient and therefore of limited use (like CopyOnWriteArrayList,
which we saw in Copy on Write, on page 34). Happily, the implementation is
much more clever than that and makes use of structure sharing.
The easiest persistent data structure to understand is the list. Here’s a simple
list:
user=> (def listv1 (list 1 2 3))
#'user/listv1
user=> listv1
(1 2 3)
And here’s a diagram of what it looks like in memory:
1 2 3
listv1
Now let’s create a modified version with cons, which returns a copy of the list
with an item added to the front:
user=> (def listv2 (cons 4 listv1))
#'user/listv2
user=> listv2
(4 1 2 3)
The new list can share all of the previous list—no copying necessary:
1 2 3
listv1
4
listv2
Finally, let’s create another modified version:
report erratumwww.it-ebooks.info
7. 리스트 전체가 아닌 일부만 사용하는 경우
복사를 통해 영속성 보장
user=> (def listv1 (list 1 2 3 4))
#’user/listv1
user=> (def listv2 (take 2 listv1))
#'user/listv2
user=> listv2
(1 2)
5
listv3
In this instance, the new list only makes use of part of the original, but
copying is still not necessary.
We can’t always avoid copying. Lists handle only common tails well—if we
want to have two lists with different tails, we have no choice but to copy.
Here’s an example:
user=> (def listv1 (list 1 2 3 4))
#'user/listv1
user=> (def listv2 (take 2 listv1))
#'user/listv2
user=> listv2
(1 2)
This leads to the following in memory:
1 2 3
listv1
4
1 2
listv2
All of Clojure’s collections are persistent. Persistent vectors, maps, and sets
are more complex to implement than lists, but for our purposes all we need
to know is that they share structure and that they provide similar performance
bounds to their nonpersistent equivalents in languages like Ruby and Java.
rwww.it-ebooks.info
16. 예제: STM을 이용한 철학자
(def philosophers (into [] (repeatedly 5 #(ref :thinking)))) ; 5명의 철학자 정의
(defn think [] (Thread/sleep (rand 1000)))
(defn eat [] (Thread/sleep (rand 1000)))
(defn philosopher-thread [n]
(Thread. #(let [philosopher (philosophers n)
left (philosophers (mod (- n 1) 5)) ; 왼쪽 철학자
right (philosophers (mod (+ n 1) 5))] ; 오른쪽 철학자
(while true (think)
(when (claim-chopsticks philosopher left right) (eat) ; 양쪽 철학자 검사
(release-chopsticks philosopher))))))
(defn -main [& args]
(let [threads (map philosopher-thread (range 5))]
(doseq [thread threads] (.start thread))
(doseq [thread threads] (.join thread))))
17. (defn release-chopsticks [philosopher]
(dosync (ref-set philosopher :thinking)))
(defn claim-chopsticks [philosopher left right]
(dosync
(when (and (= @left :thinking) (= @right :thinking))
(ref-set philosopher :eating))))
; 클로저의 STM은 서로 다른 트랜잭션이 동일한 ref를 겹쳐서 변경하지 않도록 보장
; 위의 경우 다른 ref를 참조함으로, 인접한 철학자가 동시에 식사 가능
; 이를 막기위해 ensure 사용, ref의 리턴 값이 다른 트랜잭션에서 변경되지 않았음을 보장
(defn claim-chopsticks [philosopher left right]
(dosync
(when (and (= (ensure left) :thinking) (= (ensure right) :thinking))
(ref-set philosopher :eating))))
18. 예제: 원자를 이용한 철학자
(def philosophers (atom (into [] (repeat 5 :thinking)))) ; 5명의 철학자 리스트를 원자로 생성
(defn philosopher-thread [philosopher]
(Thread. #(let [left (mod (- philosopher 1) 5) right (mod (+ philosopher 1) 5)]
(while true (think)
(when (claim-chopsticks! philosopher left right)
(eat)
(release-chopsticks! philosopher))))))
(defn release-chopsticks! [philosopher]
(swap! philosophers assoc philosopher :thinking))
(defn claim-chopsticks! [philosopher left right]
(swap! philosophers
(fn [ps] (if (and (= (ps left) :thinking) (= (ps right) :thinking))
(assoc ps philosopher :eating) ps))) ; 해당 철학자의 값을 설정후 원자에
(= (@philosophers philosopher) :eating))
19. STM vs. 원자
STM: 여러 값에 대해 조절되는 변경을 가능하게 함
원자: 단일 값에 대한 독립적인 변경을 보장
STM 해법을 원자로 바꾸는 것은 어렵지 않게 가능
가변 데이터의 최소 사용을 위해 원자의 사용을 권장
20. 정리
• 동시성 관점에서의 클로저의 장단점
• 장점: 함수형 언어이지만, 이를 벗어나는 해법도 제공
• 단점: 분산 프로그래밍을 지원하지 않음