이벤트

이벤트 (Events)

동기화 가능한 이벤트(줄여서 이벤트)는 sync 프로시저와 함께 동작해 스레드들 사이의 동기화를 조율해요. 포트(port)와 스레드(thread)를 포함한 특정 종류의 객체는 이벤트를 겸해요. 다른 종류의 객체는 이벤트로 쓰일 때만 존재해요. Racket의 이벤트 시스템은 Concurrent ML [Reppy99]에 기반해요.

출처: Racket Reference

본문

어느 시점에서든 이벤트는 동기화할 준비가 되거나, 그렇지 않아요. 이벤트의 종류와 다른 스레드가 그것을 어떻게 쓰느냐에 따라, 이벤트는 어느 때든 준비 안 됨에서 준비됨으로(또는 다시 반대로) 바뀔 수 있어요. 이벤트가 준비된 상태에서 스레드가 그것에 동기화하면, 그 이벤트는 특정한 동기화 결과를 만들어내요.

이벤트에 동기화하는 것은 이벤트의 상태에 영향을 줄 수 있어요. 예를 들어 세마포어에 동기화하면 세마포어의 내부 카운트가 semaphore-wait에서처럼 감소돼요. 그러나 대부분의 이벤트 종류(예: 포트)에 대해서는 동기화가 이벤트 상태를 변경하지 않아요.

동기화 가능한 이벤트로 동작하는 Racket 값에는 비동기 채널, 채널, 커스토디언 상자, 로그 수신기, 플레이스(place) 채널, 포트, 세마포어, 하위 프로세스, TCP 리스너, 스레드, 그리고 will 실행기가 있어요. 라이브러리는 특히 prop:evt를 통해 새 동기화 가능한 이벤트를 정의할 수 있어요.

(evt? v) → boolean?
  v : any/c

v가 동기화 가능한 이벤트이면 #t, 아니면 #f를 반환해요.

예제:

> (evt? never-evt)
#t
> (evt? (make-channel))
#t
> (evt? 5)
#f
(sync evt ...) → any
  evt : evt?

위에서 정의한 것처럼 동기화 가능한 이벤트들 evt 중 어느 것도 준비되지 않는 동안 블록돼요.

하나 이상의 evt가 준비되면 그 동기화 결과(종종 evt 자체)가 반환돼요. 여러 evt가 준비되면 그 중 하나가 의사 난수로 결과용으로 선택돼요. current-evt-pseudo-random-generator 파라미터가 이 선택을 제어하는 난수 생성기를 설정해요.

예제:

> (define ch (make-channel))
> (thread (λ () (displayln (sync ch))))
#<thread>
> (channel-put ch 'hellooooo)
hellooooo

base 패키지 6.1.0.3 버전에서 1개 이상 대신 0개의 인자를 허용하도록 바뀌었어요.

(sync/timeout timeout evt ...) → any
  timeout : (or/c #f (and/c real? (not/c negative?)) (-> any))
  evt : evt?

timeout#f이면 sync와 같아요. timeout이 실수면, 성공적인 동기화 없이 timeout 초가 지나면 결과는 #f예요. timeout이 프로시저면, evt들을 폴링해 준비된 이벤트를 찾지 못했을 때 꼬리 위치에서 호출돼요.

timeout의 0 값은 (lambda () #f)와 동등해요. 어느 경우든 #f를 반환하거나 timeout을 호출하기 전에 각 evt를 적어도 한 번 확인해요.

대안적인 타임아웃 메커니즘으로 alarm-evt도 함께 보세요.

예제:

; times out before waking up
> (sync/timeout
  0.5
  (thread (λ () (sleep 1) (displayln "woke up!"))))
#f
> (sync/timeout
  (λ () (displayln "no ready events"))
  never-evt)
no ready events

base 패키지 6.1.0.3 버전에서 2개 이상 대신 1개의 인자를 허용하도록 바뀌었어요.

(sync/enable-break evt ...) → any
  evt : evt?

sync와 같지만, evt들을 기다리는 동안 브레이크가 활성화돼요(브레이크 참고). sync/enable-break를 호출할 때 브레이크가 비활성화되어 있으면, 모든 evt가 선택되지 않은 채로 남거나 exn:break 예외가 발생하며, 둘 다는 아니에요.

(sync/timeout/enable-break timeout evt ...) → any
  timeout : (or/c #f (and/c real? (not/c negative?)) (-> any))
  evt : evt?

sync/enable-break와 같지만, sync/timeout처럼 타임아웃이 있어요.

(choice-evt evt ...) → evt?
  evt : evt?

evt들을 결합한 단일 이벤트를 만들어 반환해요. 결과를 sync에 주는 것은 각 evt를 같은 호출에 주는 것과 같아요.

즉, choice-evt가 반환한 이벤트는 choice-evt에 제공된 evt 중 하나 이상이 동기화할 준비가 될 때 준비돼요. 선택 이벤트가 선택되면, 준비된 evt 중 하나가 의사 난수로 선택되고, 동기화 결과는 선택된 evt의 동기화 결과예요.

예제:

> (define ch1 (make-channel))
> (define ch2 (make-channel))
> (define either-channel (choice-evt ch1 ch2))
> (thread (λ () (displayln (sync either-channel))))
#<thread>
> (channel-put
  (if (> (random) 0.5) ch1 ch2)
  'tuturuu)
tuturuu
(wrap-evt evt wrap) → evt?
  evt : evt?
  wrap : (any/c ... . -> . any)

evt가 동기화할 준비가 될 때 준비되는 이벤트를 만들지만, 그 동기화 결과는 evt의 동기화 결과에 wrap을 적용해 결정돼요. wrap이 받는 인자 개수는 evt의 동기화 결과 값의 개수와 일치해야 해요.

wrap 호출은 처음에 브레이크를 비활성화하도록 parameterize-breaked돼요.

예제:

> (define ch (make-channel))
> (define evt (wrap-evt ch (λ (v) (format "you've got mail: ~a" v))))
> (thread (λ () (displayln (sync evt))))
#<thread>
> (channel-put ch "Dear Alice ...")
you've got mail: Dear Alice ...
(handle-evt evt handle) → handle-evt?
  evt : evt?
  handle : (any/c ... . -> . any)

wrap-evt와 같지만, handlewrap-evt, chaperone-evt, 또는 다른 handle-evt로 감싸져 있지 않을 때 동기화 요청에 대해 꼬리 위치에서—그리고 브레이크를 명시적으로 비활성화하지 않고—호출돼요.

예제:

> (define msg-ch (make-channel))
> (define exit-ch (make-channel))
> (thread
  (λ ()
    (let loop ([val 0])
      (printf "val = ~a~n" val)
      (sync (handle-evt
             msg-ch
             (λ (val) (loop val)))
            (handle-evt
             exit-ch
             (λ (val) (displayln val)))))))
val = 0
#<thread>
> (channel-put msg-ch 5)
val = 5
> (channel-put msg-ch 7)
val = 7
> (channel-put exit-ch 'done)
done
(guard-evt maker) → evt?
  maker : (-> (or/c evt? any/c))

이벤트처럼 동작하지만 실제로는 이벤트 제작자(maker)인 값을 만들어요.

guard-evt가 반환한 이벤트 가드는 sync와 함께 쓰일 때(또는 sync와 함께 쓰이는 선택 이벤트의 일부일 때 등) 이벤트를 만들어내는데, 그 생성된 이벤트는 maker를 호출한 결과예요. maker 프로시저는 주어진 sync 호출에 대해 최대 한 번 호출될 수 있지만, 가드가 고려되기도 전에 준비된 이벤트가 선택되면 maker는 호출되지 않을 수 있어요.

maker가 비이벤트를 반환하면, maker의 결과는 동기화할 준비가 되고 동기화 결과가 guard인 이벤트로 대체돼요.

(nack-guard-evt maker) → evt?
  maker : (evt? . -> . (or/c evt? any/c))

guard-evt와 같지만, maker가 호출될 때 NACK("negative acknowledgment") 이벤트를 받아요. maker 호출을 시작한 후, maker의 이벤트가 결국 준비된 이벤트로 선택되지 않으면, maker에 제공된 NACK 이벤트가 #<void> 값으로 동기화할 준비가 돼요.

NACK 이벤트는, 다른 어떤 이벤트가 선택되었거나, 동기화 스레드가 죽었거나, 제어가 sync 호출 밖으로 빠져나갈 때(심지어 nack-guardmaker가 아직 값을 반환하지 않았더라도) 이벤트가 버려지면 동기화할 준비가 돼요. maker가 반환한 이벤트가 선택되면 NACK 이벤트는 절대 동기화할 준비가 되지 않아요.

(poll-guard-evt maker) → evt?
  maker : (boolean? . -> . (or/c evt? any/c))

guard-evt와 같지만, maker가 호출될 때 이벤트가 폴링(poll)에 쓰일지(#t) 또는 블로킹 동기화에 쓰일지(#f)를 나타내는 불리언 값을 제공받아요.

maker#t가 제공되고, 브레이크가 비활성화되어 있고, 폴링 스레드가 종료되지 않고, 결과 이벤트를 폴링해 동기화 결과를 만들어내면, 그 이벤트는 확실히 그 결과로 선택돼요.

(replace-evt evt maker) → evt?
  evt : evt?
  maker : (any/c ... . -> . (or/c evt? any/c))

guard-evt와 같지만, makerevt가 동기화할 준비가 된 뒤에만 호출되고, evt의 동기화 결과가 maker에 전달돼요.

evt에 동기화하려는 시도는 replace-evt의 결과 가드에 동기화하려는 시도와 동시에 진행돼요. 그 동시성에도 불구하고 maker가 호출되면, 가드에 동기화하는 스레드에서 호출돼요. evt와 가드와 함께 동기화된 다른 어떤 것에 동기화가 동시에 성공할 수 있어요. 동기화의 단일 선택 보장은 maker의 결과와 가드와 함께 동기화된 다른 이벤트들에만 적용돼요.

maker가 비이벤트를 반환하면, maker의 결과는 동기화할 준비가 되고 동기화 결과가 guard인 이벤트로 대체돼요.

base 패키지 6.1.0.3 버전에서 추가.

always-evt : evt?

항상 동기화할 준비가 되고, 동기화 결과가 자신인 상수 이벤트예요.

예제:

> (sync always-evt)
#<always-evt>
never-evt : evt?

절대 동기화할 준비가 되지 않는 상수 이벤트예요.

예제:

> (sync/timeout 0.1 never-evt)
#f
(system-idle-evt) → evt?

시스템이 그 외에는 유휴(idle)일 때 동기화할 준비가 되는 이벤트를 반환해요: 결과 이벤트가 never-evt로 대체된다면, 시스템의 어떤 스레드도 실행 가능하지 않을 것이에요. 즉 모든 스레드가 중단되었거나, 아직 만료되지 않은 타임아웃을 가진 이벤트에 블록되어 있어야 해요. system-idle 이벤트의 동기화 결과는 #<void>예요. system-idle-evt 프로시저의 결과는 항상 같은 이벤트예요.

예제:

> (define th (thread (λ () (let loop () (loop)))))
> (sync/timeout 0.1 (system-idle-evt))
#f
> (kill-thread th)
> (sync (system-idle-evt))
(alarm-evt msecs [monotonic?]) → evt?
  msecs : real?
  monotonic? : any/c = #f

(milliseconds)msecs보다 작은 값을 반환할 때는 동기화할 준비가 되지 않고, (milliseconds)msecs보다 큰 값을 반환할 때는 동기화할 준비가 되는 동기화 가능한 이벤트를 반환해요. monotonic?#f일 때의 milliseconds 값은 current-inexact-milliseconds이고, 그렇지 않으면 current-inexact-monotonic-milliseconds예요. 알람 이벤트의 동기화 결과는 그 알람 이벤트 자체예요.

예제:

> (define alarm (alarm-evt (+ (current-inexact-milliseconds) 100)))
> (sync alarm)
#<alarm-evt>

base 패키지 8.3.0.9 버전에서 monotonic? 인자가 추가됐어요.

(handle-evt? evt) → boolean?
  evt : evt?

evthandle-evt로 만들어졌거나, handle-evt?#t를 만드는 다른 이벤트에 choice-evt를 적용해 만들어졌으면 #t를 반환해요. 그 외의 다른 이벤트에 대해서는 handle-evt?#f를 만들어내요.

예제:

> (handle-evt? never-evt)
#f
> (handle-evt? (handle-evt always-evt values))
#t
prop:evt : struct-type-property?

인스턴스가 동기화 가능한 이벤트로 쓰일 수 있는 구조체 타입을 식별하는 구조체 타입 속성이에요. 속성 값은 다음 중 하나가 될 수 있어요.

  • 이벤트 evt: 이 경우 구조체를 이벤트로 쓰는 것은 evt를 쓰는 것과 동등해요.
  • 인자 하나를 받는 프로시저 proc: 이 경우 구조체는 guard-evt가 만든 이벤트와 비슷하지만, 잠재적인 가드 프로시저 proc이 인자 없이 대신 구조체를 인자로 받아요. 또한 proc의 비이벤트 결과는 이미 동기화할 준비가 되고 동기화 결과가 구조체인 이벤트로 대체돼요.
  • 구조체 타입의 비자동 필드 개수(상위 타입 필드 제외)보다 작은 0(포함)과 같거나 큰 정확한 음이 아닌 정수: 정수는 구조체에서 필드를 식별하고, 그 필드는 불변으로 지정되어야 해요. 필드가 객체나 인자 하나를 받는 이벤트 생성 프로시저를 담고 있으면 그 이벤트나 프로시저가 위처럼 사용돼요. 그렇지 않으면 구조체는 절대 준비되지 않는 이벤트로 동작해요.

외부 라이브러리를 다룰 때 prop:evt 값은 unsafe-poller의 결과일 수도 있는데, 다만 그 가능성은 prop:evt의 안전한 계약에서 생략돼요.

prop:input-portprop:output-port 속성을 가진 구조체 타입의 인스턴스는 포트라는 이유로 동기화 가능한 이벤트이기도 해요. 구조체 타입이 prop:evt, prop:input-port, prop:output-port 중 둘 이상을 가졌다면, 이벤트로서의 인스턴스 동작을 결정하는 데 prop:evt 값(있으면)이 우선하고, 동기화에 대해서는 prop:input-port 속성이 prop:output-port보다 우선해요.

예제:

> (struct wt (base val)
    #:property prop:evt (struct-field-index base))
> (define sema (make-semaphore))
> (sync/timeout 0 (wt sema #f))
#f
> (semaphore-post sema)
> (sync/timeout 0 (wt sema #f))
#<semaphore>
> (semaphore-post sema)
> (sync/timeout 0 (wt (lambda (self) (wt-val self)) sema))
#<semaphore>
> (semaphore-post sema)
> (define my-wt (wt (lambda (self)
                      (wrap-evt
                       (wt-val self)
                       (lambda (x) self)))
                    sema))
> (sync/timeout 0 my-wt)
#<wt>
> (sync/timeout 0 my-wt)
#f
(current-evt-pseudo-random-generator)
  → pseudo-random-generator?
(current-evt-pseudo-random-generator generator) → void?
  generator : pseudo-random-generator?

choice-evt가 만든 이벤트에 대해 sync가 사용하는 의사 난수 생성기를 결정하는 파라미터예요.

더 알아보기

  • semaphore-wait, 세마포어
  • 브레이크(Breaks): sync/enable-break
  • Concurrent ML [Reppy99]
  • prop:evt, prop:input-port, prop:output-port