관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] DispatchSemaphore (세마포어, Critical Section, wait, signal, atopic operation, 동기화) 본문

iOS 응용 (swift)

[iOS - swift] DispatchSemaphore (세마포어, Critical Section, wait, signal, atopic operation, 동기화)

jake-kim 2022. 3. 25. 23:57

Semaphore 기본 지식

  • 동시에 하나의 자원에 접근을 방지하게 할 수 있는 비동기 문제를 해결하는데 사용
  • 여러 프로세스가 데이터를 공유하면서 작업을 수행할 때, 동시에 접근하는 안되는 자원을 동시에 접근할 수 없도록 막는 플래그 기능
    * Critical Section: 동시에 접근하는 안되는 자원
  • Semaphore는 공유자원에 진입할 수 있는 허용 개수를 의미
    • (0이 되면 프로세스들의 공유 자원에 진입하는 것을 막는 것)
var semaphore = 2

// 작업 1 수행
semaphore -= 1 // 현재 semaphore = 1

// 작업 2 수행
semaphore -= 1 // 현재 semaphore = 0

// 작업 3 수행
// 현재 semaphore가 0이므로 대기

Semaphore 연산자

  • wait()
    • Semaphore의 값이 현재 0보다 크면 값을 -1
    • 카운팅 값이 0이되면 값이 증가할 때까지 대기
    • 프로세스가 자원을 사용할테니 기다리라는 의미
  • signal()
    • Semaphore의 값을 +1
    • 프로세스가 자원을 사용하고 나왔다는 신호를 의미

swift에서의 Semaphore

wait()가 걸린 상태에서 signal()을 보내면 큐에 있는 작업 동작

  • DispatchSemaphore(value:)를 사용

https://developer.apple.com/documentation/dispatch/dispatchsemaphore

  • DispatchSemaphore 인스턴스를 생성
    • value: 값은 Semaphore 값을 의미
let semaphore = DispatchSemaphore(value: 1) // 공유자원 최대 1개
var updateSomeProperty = 0 // 예제에 사용될 프로퍼티
  • 스위프트 내부적으로 wait()와 signal()의 동작 프로세스 
    • semaphore값이 0인 상태에서 다른 작업이 들어오면 현재 진행중인 큐에다가 넣어놓고 큐를 대기시키는 것
    • 주의: main queue에서 wait()를 사용하고, semaphore가 0이 되었을때, 아래 constraints코드가 안불리는 케이스가 존재
(0...3).forEach { _ in
  self.semaphore.wait()
  self.addCountAfterSeconds()
  self.semaphore.signal()
}

var count = 0
private func addCountAfterSeconds() {
  DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    self.count += 1
  }
}
  • viewDidLoad에서 wait를 부르는 작업 2가지를 시행
    • 작업 1을 실행했을 때 wait()를 한번 하는데, 이때 semaphore = 0이 되어 작업2는 진행 안되는 상황
    • 버튼을 눌렀을 때 signal()을 보내서 대기하고 있던 작업2가 동작
    • (전체 코드)
class ViewController: UIViewController {
  private lazy var someButton: UIButton = {
    let button = UIButton()
    button.setTitle("semaphore.signal() 실행", for: .normal)
    button.setTitleColor(.systemBlue, for: .normal)
    button.setTitleColor(.blue, for: .highlighted)
    button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
    button.translatesAutoresizingMaskIntoConstraints = false
    self.view.addSubview(button)
    return button
  }()
  private lazy var someLabel: UILabel = {
    let label = UILabel()
    label.textColor = .black
    label.text = "0"
    label.translatesAutoresizingMaskIntoConstraints = false
    self.view.addSubview(label)
    return label
  }()
  
  let semaphore = DispatchSemaphore(value: 1)
  var updateSomeProperty = 0
  
  override func viewDidLoad() {
    super.viewDidLoad()

    self.executeInCriticalSection()
    self.executeInCriticalSection()
    
    NSLayoutConstraint.activate([
      self.someButton.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
      self.someButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
    ])
    NSLayoutConstraint.activate([
      self.someLabel.topAnchor.constraint(equalTo: self.someButton.bottomAnchor, constant: 16),
      self.someLabel.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
    ])
  }
  
  private func executeInCriticalSection() {
    DispatchQueue.global().async {
      self.semaphore.wait()
      self.updateSomeProperty += 1
      DispatchQueue.main.async {
        self.someLabel.text = String(self.updateSomeProperty)
      }
    }
  }
  
  @objc private func didTapButton() {
    self.semaphore.signal()
  }
}

* 참고

Operating System Concepts (Abraham Silberschatz , Peter B. Galvin , Greg Gagne)

https://developer.apple.com/documentation/dispatch/dispatchsemaphore

Comments