Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

7가지 동시성 모델 4장

268 views

Published on

7가지 동시성 모델 4장 아꿈사 스터디 자료

Published in: Technology
  • Login to see the comments

7가지 동시성 모델 4장

  1. 1. Separating Identity from State (The Clojure Way) 아꿈사 Cecil
  2. 2. 목 차 • 원자(Atom) • Agent(에이전트) • STM(Software Transaction Memory) • 원자와 STM의 비교
  3. 3. 순수 함수형 언어는 가변 데이터를 지원하지 않는다 Clojure는 동시성을 염두에둔 가변 데이터를 지원 (원자, 에이전트, STM)
  4. 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. 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. 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. 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
  8. 8. 명령형 언어에서 변수(Identity) 는 변화하는 값(상태)을 가지지만, 클로저에서 한 번 상태는 변하지 않음 영속성 보장
  9. 9. 원자 etc. • 재시도 • 내부적으로 Java AtomicReference의 compareAndSet 메서드를 사용함으로 재시도가 발생할 수 있음 • 함수형 코드의 특성상 부작용이 생기지 않음 • 확인자 및 감시자 user=> (def non-negative (atom 0 :validator #(>= % 0))) ; 확인자 user=> (reset! non-negative -1) IllegalStateException Invalid reference state user=> (def a (atom 0)) user=> (add-watch a :print #(println "Changed from " %3 " to " %4)) ; 감시자 user=> (swap! a + 2) Changed from 0 to 2
  10. 10. 에이전트 user=> (def my-agent (agent 0)) ; 에이전트 정의 user=> @my-agent ; 값 조회 0 user=> (send my-agent inc) ; 에이전트에 inc 함수 적용 user=> @my-agent 1 user=> (send my-agent + 2) ; 에이전트에 + 함수 적용 user=> @my-agent 3 • 원자와의 차이점: send • 에이전트의 값이 변하기 전에 바로 리턴 됨
  11. 11. • 동작이 완료되기를 기다리기 • 에러 처리 에이전트 etc. user=> (def my-agent (agent 0))
 #'user/my-agent
 user=> (send my-agent #((Thread/sleep 2000) (inc %)))
 user=> (await my-agent) ; 동작이 완료되기를 기다림
 nil
 user=> @my-agent
 1 user=> (def non-negative (agent 0 :validator (fn [new-val] (>= new-val 0)))) ; 확인자 정의 user=> (send non-negative dec) ; -1이 되어 실패 상태가 됨. user=> @non-negative 
 0 user=> (send non-negative inc) ; 실패 상태에서는 값 변경 안됨 IllegalStateException Invalid reference state clojure.lang.ARef.validate... user=> (agent-error non-negative) ; 에러 내용 조회 #<IllegalStateException java.lang.IllegalStateException: Invalid reference state> user=> (restart-agent non-negative 0) ; 에이전트 다시 시작 user=> (agent-error non-negative) nil
  12. 12. 예제: 인메모리 로그 (def log-entries (agent [])) ; 로그 수집을 위한 Agent (defn log [entry] (send log-entries conj [(now) entry])) ; agent로 로그 전송 logger.core=> (log "Something happened”) #<Agent@bd99597: [[1366822537794 "Something happened”]]> logger.core=> (log "Something else happened”) #<Agent@bd99597: [[1366822538932 "Something happened”]]> logger.core=> @log-entries [[1366822537794 "Something happened"] [1366822538932 "Something else happened"]]
  13. 13. • ref를 정의하고, 트랜잭션 내에서만 변경 가능 트랜잭션 메모리(STM) user=> (def my-ref (ref 0)) ; ref 정의 user=> @my-ref 0 user=> (ref-set my-ref 42) ; ref 값 설정 시도 IllegalStateException No transaction running user=> (alter my-ref inc) ; ref 함수 적용 시도 IllegalStateException No transaction running user=> (dosync (ref-set my-ref 42)) ; 트랜잭션 실행 (dosync) user=> @my-ref 42 user=> (dosync (alter my-ref inc)) user=> @my-ref 43
  14. 14. STM은 3가지 트랜잭션 속성을 지원 원자성(Atomic) 일관성(Consistent) 고립성(Isolated) 지속성(Durability) 지원 안함
  15. 15. 트랜잭션 재시도 • 트랜잭션이 모순되는 변경을 감지할 경우 재시도 • 에이전트 SEND는 트랙잭션이 성공할 경우에만 호출이 됨. (def attempts (atom 0)) ; 재시도 횟수 기록 (def transfers (agent 0)) ; 전송 횟수 기록 (defn transfer [from to amount] (dosync (swap! attempts inc) // Side-effect (send transfers inc) (alter from - amount) (alter to + amount))) (def checking (ref 10000)) (def savings (ref 20000)) (defn stress-thread [from to iterations amount]
 (Thread. #(dotimes [_ iterations] (transfer from to amount)))) (defn -main [& args] (println "Before: Checking =" @checking " Savings =" @savings) (let [t1 (stress-thread checking savings 100 100) t2 (stress-thread savings checking 200 100)] (.start t1) (.start t2) (.join t1) (.join t2)) (await transfers) (println "Attempts: " @attempts) ; 300 이상 (println "Transfers: " @transfers) ; 300 (println "After: Checking =" @checking " Savings =" @savings))
  16. 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. 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. 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. 19. STM vs. 원자 STM: 여러 값에 대해 조절되는 변경을 가능하게 함 원자: 단일 값에 대한 독립적인 변경을 보장 STM 해법을 원자로 바꾸는 것은 어렵지 않게 가능 가변 데이터의 최소 사용을 위해 원자의 사용을 권장
  20. 20. 정리 • 동시성 관점에서의 클로저의 장단점 • 장점: 함수형 언어이지만, 이를 벗어나는 해법도 제공 • 단점: 분산 프로그래밍을 지원하지 않음
  21. 21. References •Paul Butcher, 7가지 동시성 모델(임백준 옮김). 서울 시 마포구 양화로 한빛미디어, 2016.

×