2015년 12월 17일 목요일

Swift - GCD 세마포어(Dispatch Semaphore)

세마포어도 병렬 프로그래밍 동기화를 위한 전통적인 기능 중 하나이다. GCD(Grand Central Dispatch) Dispatch 에서도 세마포어가 제공되기에 이를 간단히 살펴보고자 한다.

세마포어는 이론적인 면에서 접근하면 뭔가 복잡하지만, 그냥 단순히 wait-signal 스타일 형식으로 보통은 교통신호기로 비유하기도 한다. 빨간불이면 멈추고(wait) 파란불이면 통과(signal)이다. 즉 멀티스레딩 시 동기화를 맞추는 방식으로 사용하게 된다는 점만 파악하면 어려울 건 없다. 활용 방법이야 쓰기에 따라 무궁무진 하겠지만...

Swift 3 들어서 DispatchSemaphore 라는 클래스로 새롭게 디자인 되면서 이 세마포어의 설명에는 고전적인 숫자세기형(?) 세마포어(Transitional Counting Semaphore) 라는게 붙어있다. 이 의미는 signal()이 불리면 값이 증가하고 wait() 가 불리면 값이 감소하는 마치 레퍼런스 카운팅 방식과 비슷한 동작을 이용할 수 있다. 하지만 대게는 이 값을 이용하지 않는게 더 편하게 쓰일 것 같다.

DispatchSemaphore

let s = DispatchSemaphore(value: 0)
초기값을 0을 가지는 세마포어를 생성하는 예제이다.

이 초기 value 의 값은 여러 용도로 사용할 수 있는데, 대체로 두개 이상의 스레드를 동기 시키기 위해서는 0을 넣는다. 이렇게 하면 signal과 wait 쌍을 맞출 수 있게 되어 wait에서 깨어나는 시점을 동기화 할 수 있기 때문이다.

wait

s.wait()
wait 를 호출하게 되면 value 값이 감소하게 된다. 만약 0 이라면 signal() 이 불릴 때 까지 대기하게 된다.

참고로 이 메소드는 타임아웃과 관련한 여러 모양이 존재한다.

signal

s.signal()
signal 을 호출하게 되면 value 값이 증가한다.

예제

그다지 쓸 만한 예제는 잘 모르겠지만, 대표적으로 세마포어를 활용해 비동기 코드를 동기 코드로 만들 수 있다.

여기서는 NSURLSession 을 이용해 네트워크 통신을 비동기로 수행하는 경우를 보자. 코드는 정말 필요한 것만 추려놓은 코드(Pseudo-Code)이다.
let session = NSURLSession.sharedSession()
...
let task = session.dataTaskWithRequest(someRequest) {
  data, response, error in
  if let data = data {
    print("I've got data!")
    ...
  }
}

task.resume()
print("end")
NSURLSession을 안다면야 단순한 코드일 것이다. 내용은 그냥 someRequest를 dataTask 형태로 요청해서 데이터를 받아오는 건데, dataTaskWithRequest 메소드의 꼬리 클로져(Trailing Closure)로 선언된 부분은 비동기로 호출된다. 따라서 위 코드가 실행되면 콘솔에는 "end" 가 먼저 출력되고 나중에 "I've got data!" 가 출력될 것이다.

만약 이런 코드를 동기 코드로 바꾸고자 한다면 세마포어를 활용할 수 있다.
let session = NSURLSession.sharedSession()
let semaphore = DispatchSemaphore(value: 0)

let task = session.dataTaskWithRequest(someRequest) {
  data, response, error in
  if let data = data {
    print("I've got data!")
    ...
  }
  semaphore.signal()
}

task.resume()
semaphore.wait()
print("end")
이 코드가 실행되면 이제 콘솔에는 "I've got data!" 가 먼저 출력된 후 "end"가 출력된다. 즉, semaphore.wait() 는 signal이 올 때 까지 해당 라인에서 무한 대기를 타게 만든다.

따라서 위 코드는 비동기 루틴을 동기 루틴으로 바꿔버린 효과가 발생한다. 물론 동기코드의 문제점, 즉 통신 도중에는 UI가 얼어버리는 현상이 발생하기에 매우 부적절한 코드이긴 하지만 말이다.

마무리

세마포어도 뮤텍스(Mutex) 처럼 락 유발자다. 잘 쓰면 백그라운드 스레드 끼리 문제없게 잘 굴러가게 만들지만 서로 꼬이고 꼬여서 전부 멈춰버리는 현상을 발생시킬 수도 있다. 그러니 가급적 단순한 디자인에서 사용하는 것이 좋긴 좋다.

마무리를 하려니 별로 쓸 만한게 안떠오른다. 위의 이야기도 그냥 상식적인 이야기이고 음 -_-;; 그냥 이렇게 마무리한다.

[관련글] Swift - GCD(Grand Central Dispatch) 기초
[관련글] Swift - 병렬 프로그래밍(Concurrency Pprogramming) 가이드
[관련글] 스위프트(Swift) 가이드

댓글 없음 :