Clojure
6장. Concurrency
아키텍트를 꿈꾸는 사람들 cafe.naver.com/architect1
현수명 soomong.net
#soomong
목차
병행성
공유된 자원
변수 만들기 실습
다른언어로 변수만들기
병행성 제어 Lib 4인방
ref , atom , agent , var
Example – Snake
Example – Lancet
Erlang 의 병행성
병행성?
여러가지를 동시에 일어나게 만드는 일
병행성!
어렵지 않음.
여러색의Thread 나 Process 를 띄우면 되요
문제는,
동시에 일어나는 일들
즉 도화지의
변화하는 상태를
어떻게 제어할 것인가
공유된 자원
공유된 자원?
Clojure 에서 변수를 어떻게 만들지?
변수!
변수 만들기 실습
(def X 5)
#’user/X
X
5
(def X 3)
#’user/X
(def ^{:user/comment "A"} x 5 )
(meta #’user/x)
{:ns #<Namespace user>,
:name x,
:file "NO_SOURCE_PATH",
:line 30,
:user/comment "A"}
(def x 3)
(meta #’user/x)
{:ns #<Namespace user>,
:name x,
:file "NO_SOURCE_PATH",
:line 32}
다른언어로 변수만들기
int x = 5;
x = 3;
Clojure 에서는?
데이터의 상태가 기본적으로 변경불가능
Clojure 에는 직접 변경할수있는 변수 없음
But. 예외가 있음
코드에서 명백하게 선언해야한다는
조건하에 간접적으로 변경 가능한
병행성 API 들을 제공
보통의 다른 언어들
모든 상태는 변경가능
상태변화가 코드전체에 뒤섞여 있음
병행성은Thread 로 구현
여러Thread 가 접근하는 경우
적절한 락을 통해
한번에 하나만 접근하도록 보호
int x = 5;
x = 3;
목차
병행성
공유된 자원
변수 만들기 실습
다른언어로 변수만들기
병행성 제어 Lib 4인방
ref , atom , agent , var
Example – Snake
Example – Lancet
Erlang 의 병행성
병행성 제어 Lib 4인방
ref + STM 제어 O 동기적 갱신
atom 제어 X 동기적 갱신
agent 제어 X 비동기적 갱신
var Thread 내부의 동적범위
ref
(def amount 3)
(ref amount)
(deref amount)
java.lang.ClassCastException: java.lang.Integer
cannot be cast to clojure.lang.IDeref (repl-1:33)
(def ref_amount (ref amount))
#'user/ref_amount
(deref ref_amount)
3
(alter ref_amount dec)
java.lang.IllegalStateException: No transaction
running (repl-1:40)
; 갱신할때 적절한 보호를 해주어야한다.
; Clojure 는 트랜잭션을 사용해서 보호
(dosync (alter ref_amount dec))
2
Clojure 의 병행성 제어 Lib 1
API ref
deref
ref-set
dosync
alter
commute
:validator
Clojure 의 병행성 제어 Lib 1
API ref
deref
ref-set
dosync
alter
commute
:validator
(def ref_amount (ref amount))
#'user/ref_amount
(deref ref_amount)
3
(ref-set ref_amount (def size 5)
java.lang.IllegalStateException: No transaction
running
; 갱신할때 적절한 보호를 해주어야한다.
; Clojure 는 트랜잭션을 사용해서 보호
(dosync (ref-set ref_amount (def size 5))
#'user/size
ref
Clojure 의 잠금
다른 언어에서는 데이터 보호를 위해
Lock 을 사용
Clojure 에서는 데이터 보호를 위해
Transaction 을 사용
STM Software Transaction Memory
Atomicity 원자성
Consistency 일관성
Isolation 고립성
Durability 영구성
Database 는 ACID
Clojure 는 ACI
ref
Clojure 의 병행성 제어 Lib 1
API ref
deref
ref-set
dosync
alter
commute
:validator
(def amount 3)
(def ref_amount (ref amount))
#'user/ref_amount
(def size 10)
(def ref_size (ref size))
#'user/ref_size
(dosync
(ref-set ref_amount 6)
(ref-set ref_size 20))
20
; 갱신이 하나의 트랜잭션 안에서 수행됨
ref
Clojure 의 병행성 제어 Lib 1
API ref
deref
ref-set
dosync
alter
commute
:validator
(defstruct message :sender :text)
(struct message "stu" "testmsg")
{:sender "stu", :text "testmsg"}
(def messages (ref ()))
#'user/messages
(defn native-add-msg [msg]
(dosync (ref-set messages (cons msg @messages))))
(defn add-msg [msg]
(dosync (alter messages conj msg)))
(add-msg (struct message "user1" "hello"))
({:sender "user1", :text "hello"})
(add-msg (struct message "user2" "hoho"))
({:sender "user2", :text "hoho"}
{:sender "user1", :text "hello"})
ref
Clojure 의 병행성 제어 Lib 1
API ref
deref
ref-set
dosync
alter
commute
:validator
(defn add-msg-commute [msg]
(dosync (commute messages conj msg)))
; 트랜잭션의 순서가 마음대로 재배열됨
(def validate-msg-list
(partial every? #(and (:sender %) (:text %))))
(def messages (ref () :validator validate-msg-list))
; 유효성 확인 추가
(add-message "not a valid msg")
java.lang.IllegalStateException: Invalid
reference state
(add-message (struct message "stu" "good message"))
({:sender "stu", :text "good message"})
병행성 제어 Lib 4인방
ref + STM 제어 O 동기적 갱신
atom 제어 X 동기적 갱신
agent 제어 X 비동기적 갱신
var Thread 내부의 동적범위
atom
Clojure 의 병행성 제어 Lib 2
API atom
reset!
swap!(def amount 3)
(def atom_amount (atom amount))
#'user/atom_amount
(deref atom_amount)
3
(reset! atom_amount 0)
0
; atom 은 트랜잭션 내에서 변경되는것이 아니기 때문에
; dosync 를 사용할 필요 없음 ( 제어 X )
; 동시에 2개의 atom 을 변경시키는것은 불가능!
; 이것이 ref 와의 차이점
(def atom_track (atom {:title "Hoot" :singer "SOSI"}))
#'user/atom_track
(swap! atom_track assoc :title "Huk")
병행성 제어 Lib 4인방
ref + STM 제어 O 동기적 갱신
atom 제어 X 동기적 갱신
agent 제어 X 비동기적 갱신
var Thread 내부의 동적범위
agent
Clojure 의 병행성 제어 Lib 3
API agent
send
await
:validator
clear-agent
(def counter (agent 0))
#'user/counter
(send counter inc)
#<Agent@482d59a3: 1>
; 비동기적으로 갱신되므로 inc 를 나중에 처리한다
(deref counter)
1
; 이값을 확인할때쯤이면 inc 는 이미 thread 풀에서 작업을 완료
; dosync 를 사용할 필요 없음 ( 제어 X )
(await counter)
; agent 에 대한 작업완료시까지 현재 수행되는 thread 를 block
agent
Clojure 의 병행성 제어 Lib 3
API agent
send
await
:validator
clear-agent-errors
(def counter (agent 0 :validator number?))
#'user/counter
(send counter (fn [_] "boo"))
#<Agent@5dde45e2 FAILED: 0>
; 유효성 검사에서 실패
(agent-errors counter)
(#<IllegalStateException
java.lang.IllegalStateException: Invalid reference
state>)
(clear-agent-errors counter)
0
병행성 제어 Lib 4인방
ref + STM 제어 O 동기적 갱신
atom 제어 X 동기적 갱신
agent 제어 X 비동기적 갱신
var Thread 내부의 동적범위
var
Clojure 의 병행성 제어 Lib 4
API binding
set!
(def foo 10)
#'user/foo
foo
10
(.start (Thread. (fn [] (println foo))))
nil
10
(binding [foo 42] foo)
42
(defn print-foo [] (println foo))
(let [foo “let foo”] (print-foo))
10
(binding [foo “bound foo”] (print-foo))
bound foo
var
Clojure 의 병행성 제어 Lib 4
API binding
set!(defn slow-double [n]
(Thread/sleep 100)
(* n 2))
(defn calls-slow-double []
(map slow-double [1 2 1 2 1 2]))
(time (dorun (calls-slow-double)))
"Elapsed time: 608.778399 msecs“
(defn demo-memoize []
(time (dorun
(binding [slow-double (memoize slow-double)]
(calls-slow-double)))))
; memoize 를 동적 바인딩으로 calls-slow-double 수정없이 교체!
(demo-memoize)
"Elapsed time: 202.116447 msecs"
병행성 제어 Lib 4인방
ref + STM 제어 O 동기적 갱신
atom 제어 X 동기적 갱신
agent 제어 X 비동기적 갱신
var Thread 내부의 동적범위
Java lock 제어 O 동기적 갱신
Clojure 의 병행성
상태변화가 없는 함수형 모델
상태 변화가 존재하는 모델
병행성 API 를 사용하지 않은 부분
병행성API 를 사용한 부분
목차
병행성
공유된 자원
변수 만들기 실습
다른언어로 변수만들기
병행성 제어 Lib 4인방
ref , atom , agent , var
Example – Snake
Example – Lancet
Erlang 의 병행성
Example - Snake
(defn reset-game [snake apple]
(dosync (ref-set apple (create-apple))
(ref-set snake (create-snake)))
nil)
(defn update-direction [snake newdir]
(when newdir (dosync (alter snake turn newdir))))
(defn update-positions [snake apple]
(dosync
(if (eats? @snake @apple)
(do (ref-set apple (create-apple))
(alter snake move :grow))
(alter snake move)))
nil)
Example - Lancet
API locking
; START: create-runonce
(defn create-runonce [function]
(let [sentinel (Object.)
result (atom sentinel)]
(fn [& args]
(locking sentinel
(if (= @result sentinel)
(reset! result (function))
@result)))))
; END: create-runonce
Erlang 의 병행성
병행성 지향 프로그래밍
• Concurrency-oriented programming
• 처음부터 병행성을 고려해서 만든 언어.
• 공유 메모리가 없음
• 잠금 매커니즘 필요없음
• 쉽게 병행 프로그래밍 가능
Erlang
단일 할당 변수
1> X.
1: variable ‘X' is unbound
2> X = 50.
50
3> X = 23.
** exception error: no match of right
hand side value 23
• 변수가 아니라 write-once 변수
• 즉 한번만 bound 를 할 수 있음
Erlang
패턴매칭
X = 50.
• = 은 할당연산자가 아니라 패턴매칭연산
자.
• 오른쪽을 평가해서 그 결과를 왼쪽에 있는
패턴과 매치하라는 뜻.
Erlang
왜 단일 할당이 프로그램을
더 낫게 만드는가?
• 변수의 값을 바꿀수 있다?
– 누가 언제 바꿨는지 깔끔하게 알기힘듬.
– 복잡한 잠금 매커니즘 필요.
• 변수의 값을 바꿀수 없다?
– 불변상태의 메모리는 read 만 가능.
– 잠금 매커니즘이 필요없다.
• 즉 쉬운 병행 프로그래밍 가능
– 어떻게? 프로세스 메시지 전달 방식으로.
Erlang
Pure message passing language
• 순수 메시지 전달 언어
• 명령어 3개로 병행프로그래밍 가능
spawn : 프로세스 생성
! (send) : 메시지 보내기
receive : 메시지 받기
Erlang
$ erl
1> Pid = spawn(fun area:loop/0).
<0.45.0>
2> Pid ! {rect, 6, 10}.
rect : 60
3> Pid ! {circle, 5}.
circle : 78.5
4> Pid ! {triangle, 2, 4, 5}.
what is it? triangle?
Erlang
area.erl
-module(area).
-export([loop/0]).
loop() ->
receive
{rect, Width, Ht} ->
io:format(“rect : ~p~n”, [Width * Ht]),
loop();
{circle, R} ->
io:format(“circle : ~p~n”, [3.14 * R *
R]),
loop();
Other ->
io:format(“what is it? ~p?~n”, [Other]),
loop()
end.
Erlang
Reference
Programming Clojure – 인사이트
병행성 색연필
http://www.flickr.com/photos/kayyali/5121460008/
http://www.flickr.com/photos/queensy/4630729220/
Programming Erlang - 인사이트
감사합니다

Clojure Chapter.6

  • 1.
    Clojure 6장. Concurrency 아키텍트를 꿈꾸는사람들 cafe.naver.com/architect1 현수명 soomong.net #soomong
  • 2.
    목차 병행성 공유된 자원 변수 만들기실습 다른언어로 변수만들기 병행성 제어 Lib 4인방 ref , atom , agent , var Example – Snake Example – Lancet Erlang 의 병행성
  • 3.
  • 4.
    병행성! 어렵지 않음. 여러색의Thread 나Process 를 띄우면 되요 문제는, 동시에 일어나는 일들 즉 도화지의 변화하는 상태를 어떻게 제어할 것인가
  • 5.
    공유된 자원 공유된 자원? Clojure에서 변수를 어떻게 만들지? 변수!
  • 6.
    변수 만들기 실습 (defX 5) #’user/X X 5 (def X 3) #’user/X (def ^{:user/comment "A"} x 5 ) (meta #’user/x) {:ns #<Namespace user>, :name x, :file "NO_SOURCE_PATH", :line 30, :user/comment "A"} (def x 3) (meta #’user/x) {:ns #<Namespace user>, :name x, :file "NO_SOURCE_PATH", :line 32}
  • 7.
  • 8.
    Clojure 에서는? 데이터의 상태가기본적으로 변경불가능 Clojure 에는 직접 변경할수있는 변수 없음 But. 예외가 있음 코드에서 명백하게 선언해야한다는 조건하에 간접적으로 변경 가능한 병행성 API 들을 제공
  • 9.
    보통의 다른 언어들 모든상태는 변경가능 상태변화가 코드전체에 뒤섞여 있음 병행성은Thread 로 구현 여러Thread 가 접근하는 경우 적절한 락을 통해 한번에 하나만 접근하도록 보호 int x = 5; x = 3;
  • 10.
    목차 병행성 공유된 자원 변수 만들기실습 다른언어로 변수만들기 병행성 제어 Lib 4인방 ref , atom , agent , var Example – Snake Example – Lancet Erlang 의 병행성
  • 11.
    병행성 제어 Lib4인방 ref + STM 제어 O 동기적 갱신 atom 제어 X 동기적 갱신 agent 제어 X 비동기적 갱신 var Thread 내부의 동적범위
  • 12.
    ref (def amount 3) (refamount) (deref amount) java.lang.ClassCastException: java.lang.Integer cannot be cast to clojure.lang.IDeref (repl-1:33) (def ref_amount (ref amount)) #'user/ref_amount (deref ref_amount) 3 (alter ref_amount dec) java.lang.IllegalStateException: No transaction running (repl-1:40) ; 갱신할때 적절한 보호를 해주어야한다. ; Clojure 는 트랜잭션을 사용해서 보호 (dosync (alter ref_amount dec)) 2 Clojure 의 병행성 제어 Lib 1 API ref deref ref-set dosync alter commute :validator
  • 13.
    Clojure 의 병행성제어 Lib 1 API ref deref ref-set dosync alter commute :validator (def ref_amount (ref amount)) #'user/ref_amount (deref ref_amount) 3 (ref-set ref_amount (def size 5) java.lang.IllegalStateException: No transaction running ; 갱신할때 적절한 보호를 해주어야한다. ; Clojure 는 트랜잭션을 사용해서 보호 (dosync (ref-set ref_amount (def size 5)) #'user/size ref
  • 14.
    Clojure 의 잠금 다른언어에서는 데이터 보호를 위해 Lock 을 사용 Clojure 에서는 데이터 보호를 위해 Transaction 을 사용
  • 15.
    STM Software TransactionMemory Atomicity 원자성 Consistency 일관성 Isolation 고립성 Durability 영구성 Database 는 ACID Clojure 는 ACI
  • 16.
    ref Clojure 의 병행성제어 Lib 1 API ref deref ref-set dosync alter commute :validator (def amount 3) (def ref_amount (ref amount)) #'user/ref_amount (def size 10) (def ref_size (ref size)) #'user/ref_size (dosync (ref-set ref_amount 6) (ref-set ref_size 20)) 20 ; 갱신이 하나의 트랜잭션 안에서 수행됨
  • 17.
    ref Clojure 의 병행성제어 Lib 1 API ref deref ref-set dosync alter commute :validator (defstruct message :sender :text) (struct message "stu" "testmsg") {:sender "stu", :text "testmsg"} (def messages (ref ())) #'user/messages (defn native-add-msg [msg] (dosync (ref-set messages (cons msg @messages)))) (defn add-msg [msg] (dosync (alter messages conj msg))) (add-msg (struct message "user1" "hello")) ({:sender "user1", :text "hello"}) (add-msg (struct message "user2" "hoho")) ({:sender "user2", :text "hoho"} {:sender "user1", :text "hello"})
  • 18.
    ref Clojure 의 병행성제어 Lib 1 API ref deref ref-set dosync alter commute :validator (defn add-msg-commute [msg] (dosync (commute messages conj msg))) ; 트랜잭션의 순서가 마음대로 재배열됨 (def validate-msg-list (partial every? #(and (:sender %) (:text %)))) (def messages (ref () :validator validate-msg-list)) ; 유효성 확인 추가 (add-message "not a valid msg") java.lang.IllegalStateException: Invalid reference state (add-message (struct message "stu" "good message")) ({:sender "stu", :text "good message"})
  • 19.
    병행성 제어 Lib4인방 ref + STM 제어 O 동기적 갱신 atom 제어 X 동기적 갱신 agent 제어 X 비동기적 갱신 var Thread 내부의 동적범위
  • 20.
    atom Clojure 의 병행성제어 Lib 2 API atom reset! swap!(def amount 3) (def atom_amount (atom amount)) #'user/atom_amount (deref atom_amount) 3 (reset! atom_amount 0) 0 ; atom 은 트랜잭션 내에서 변경되는것이 아니기 때문에 ; dosync 를 사용할 필요 없음 ( 제어 X ) ; 동시에 2개의 atom 을 변경시키는것은 불가능! ; 이것이 ref 와의 차이점 (def atom_track (atom {:title "Hoot" :singer "SOSI"})) #'user/atom_track (swap! atom_track assoc :title "Huk")
  • 21.
    병행성 제어 Lib4인방 ref + STM 제어 O 동기적 갱신 atom 제어 X 동기적 갱신 agent 제어 X 비동기적 갱신 var Thread 내부의 동적범위
  • 22.
    agent Clojure 의 병행성제어 Lib 3 API agent send await :validator clear-agent (def counter (agent 0)) #'user/counter (send counter inc) #<Agent@482d59a3: 1> ; 비동기적으로 갱신되므로 inc 를 나중에 처리한다 (deref counter) 1 ; 이값을 확인할때쯤이면 inc 는 이미 thread 풀에서 작업을 완료 ; dosync 를 사용할 필요 없음 ( 제어 X ) (await counter) ; agent 에 대한 작업완료시까지 현재 수행되는 thread 를 block
  • 23.
    agent Clojure 의 병행성제어 Lib 3 API agent send await :validator clear-agent-errors (def counter (agent 0 :validator number?)) #'user/counter (send counter (fn [_] "boo")) #<Agent@5dde45e2 FAILED: 0> ; 유효성 검사에서 실패 (agent-errors counter) (#<IllegalStateException java.lang.IllegalStateException: Invalid reference state>) (clear-agent-errors counter) 0
  • 24.
    병행성 제어 Lib4인방 ref + STM 제어 O 동기적 갱신 atom 제어 X 동기적 갱신 agent 제어 X 비동기적 갱신 var Thread 내부의 동적범위
  • 25.
    var Clojure 의 병행성제어 Lib 4 API binding set! (def foo 10) #'user/foo foo 10 (.start (Thread. (fn [] (println foo)))) nil 10 (binding [foo 42] foo) 42 (defn print-foo [] (println foo)) (let [foo “let foo”] (print-foo)) 10 (binding [foo “bound foo”] (print-foo)) bound foo
  • 26.
    var Clojure 의 병행성제어 Lib 4 API binding set!(defn slow-double [n] (Thread/sleep 100) (* n 2)) (defn calls-slow-double [] (map slow-double [1 2 1 2 1 2])) (time (dorun (calls-slow-double))) "Elapsed time: 608.778399 msecs“ (defn demo-memoize [] (time (dorun (binding [slow-double (memoize slow-double)] (calls-slow-double))))) ; memoize 를 동적 바인딩으로 calls-slow-double 수정없이 교체! (demo-memoize) "Elapsed time: 202.116447 msecs"
  • 27.
    병행성 제어 Lib4인방 ref + STM 제어 O 동기적 갱신 atom 제어 X 동기적 갱신 agent 제어 X 비동기적 갱신 var Thread 내부의 동적범위 Java lock 제어 O 동기적 갱신
  • 28.
    Clojure 의 병행성 상태변화가없는 함수형 모델 상태 변화가 존재하는 모델 병행성 API 를 사용하지 않은 부분 병행성API 를 사용한 부분
  • 29.
    목차 병행성 공유된 자원 변수 만들기실습 다른언어로 변수만들기 병행성 제어 Lib 4인방 ref , atom , agent , var Example – Snake Example – Lancet Erlang 의 병행성
  • 30.
    Example - Snake (defnreset-game [snake apple] (dosync (ref-set apple (create-apple)) (ref-set snake (create-snake))) nil) (defn update-direction [snake newdir] (when newdir (dosync (alter snake turn newdir)))) (defn update-positions [snake apple] (dosync (if (eats? @snake @apple) (do (ref-set apple (create-apple)) (alter snake move :grow)) (alter snake move))) nil)
  • 31.
    Example - Lancet APIlocking ; START: create-runonce (defn create-runonce [function] (let [sentinel (Object.) result (atom sentinel)] (fn [& args] (locking sentinel (if (= @result sentinel) (reset! result (function)) @result))))) ; END: create-runonce
  • 32.
  • 33.
    병행성 지향 프로그래밍 •Concurrency-oriented programming • 처음부터 병행성을 고려해서 만든 언어. • 공유 메모리가 없음 • 잠금 매커니즘 필요없음 • 쉽게 병행 프로그래밍 가능 Erlang
  • 34.
    단일 할당 변수 1>X. 1: variable ‘X' is unbound 2> X = 50. 50 3> X = 23. ** exception error: no match of right hand side value 23 • 변수가 아니라 write-once 변수 • 즉 한번만 bound 를 할 수 있음 Erlang
  • 35.
    패턴매칭 X = 50. •= 은 할당연산자가 아니라 패턴매칭연산 자. • 오른쪽을 평가해서 그 결과를 왼쪽에 있는 패턴과 매치하라는 뜻. Erlang
  • 36.
    왜 단일 할당이프로그램을 더 낫게 만드는가? • 변수의 값을 바꿀수 있다? – 누가 언제 바꿨는지 깔끔하게 알기힘듬. – 복잡한 잠금 매커니즘 필요. • 변수의 값을 바꿀수 없다? – 불변상태의 메모리는 read 만 가능. – 잠금 매커니즘이 필요없다. • 즉 쉬운 병행 프로그래밍 가능 – 어떻게? 프로세스 메시지 전달 방식으로. Erlang
  • 37.
    Pure message passinglanguage • 순수 메시지 전달 언어 • 명령어 3개로 병행프로그래밍 가능 spawn : 프로세스 생성 ! (send) : 메시지 보내기 receive : 메시지 받기 Erlang
  • 38.
    $ erl 1> Pid= spawn(fun area:loop/0). <0.45.0> 2> Pid ! {rect, 6, 10}. rect : 60 3> Pid ! {circle, 5}. circle : 78.5 4> Pid ! {triangle, 2, 4, 5}. what is it? triangle? Erlang
  • 39.
    area.erl -module(area). -export([loop/0]). loop() -> receive {rect, Width,Ht} -> io:format(“rect : ~p~n”, [Width * Ht]), loop(); {circle, R} -> io:format(“circle : ~p~n”, [3.14 * R * R]), loop(); Other -> io:format(“what is it? ~p?~n”, [Other]), loop() end. Erlang
  • 40.
    Reference Programming Clojure –인사이트 병행성 색연필 http://www.flickr.com/photos/kayyali/5121460008/ http://www.flickr.com/photos/queensy/4630729220/ Programming Erlang - 인사이트
  • 41.