경합 상황에서 동기화 방법으로 문제 해결하기
import UIKit
class ViewController: UIViewController {
var value = 0
let firstQueue = DispatchQueue(label: "First", attributes: .concurrent)
let secondQueue = DispatchQueue(label: "Second", attributes: .concurrent)
let group = DispatchGroup()
@IBOutlet weak var valueLabel: UILabel!
@IBAction func start(_ sender: Any) {
value = 0
for _ in 1 ... 1000{
firstQueue.async(group: group, execute: {
self.value += 1
})
secondQueue.async(group: group, execute: {
self.value += 1
})
}
group.notify(queue: .main){
self.valueLabel.text = "\(self.value)"
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
이러한 상황에서 우리는 2000이 출력될 것이라고 생각한다.
하지만 동기화가 되지 않았기 때문에 결과는 기대와는 다르게 나온다.
두 개의 스레드에서 동시에 동일한 값에 접근하면 결과는 알 수 없어진다.
위와 같은 문제를 Race Condition(경합 상황)
이라고 한다.
그리고 Race Condition이 발생하는 범위를 Critical Section
이라고 한다.
for _ in 1 ... 1000{
firstQueue.async(group: group, execute: {
self.value += 1 //Critical Section
})
...
항상 똑같은 결과를 얻고 싶다면 속성을 동기화해야 한다.
sync
를 사용하여 직렬화한다.let firstQueue = DispatchQueue(label: "First") //concurrent 사용해도 상관없음
let secondQueue = DispatchQueue(label: "Second")
let syncQueue = DispatchQueue(label: "Sync")
@IBAction func start(_ sender: Any) {
value = 0
for _ in 1 ... 1000{
firstQueue.async(group: group, execute: {
self.syncQueue.sync { //sync로 직렬화
self.value += 1
}
})
secondQueue.async(group: group, execute: {
self.syncQueue.sync {
self.value += 1
}
})
}
group.notify(queue: .main){
self.valueLabel.text = "\(self.value)"
}
}
Mutual Exclusion
의 약자NSLock
클래스와 DispatchSemaphore
로 구현 가능하다.atomically
라는 뜻은 1. 실행이 시작되면 중간에 중단할 수 없으며 중간에 다른 연산이 끼어들 수 없음, 2. 실행 결과가 다른 결과에 독립적임 을 의미한다.lock
: 이미 다른 스레드가 lock중이라면 unlock될때까지 대기한다. 따라서 이 메소드를 메인스레드에서 호출할 때는 조심해야 한다.unlock
: 이전에 잠갔던 부분을 해제한다.lock
과 비슷한 역할을 하지만, 스레드를 블로킹하지 않는다는 특징이 있다. let lock = NSLock()
@IBAction func mutex(_ sender: Any) {
value = 0
for _ in 1 ... 1000{
firstQueue.async(group: group, execute: {
self.lock.lock()
self.value += 1
self.lock.unlock()//lock한 스레드와 같은 스레드에서 unlcok 필수
})
secondQueue.async(group: group, execute: {
self.lock.lock()
self.value += 1
self.lock.unlock()
})
}
group.notify(queue: .main){
self.valueLabel.text = "\(self.value)"
}
}
init
에 전달하는 정수는 공유 리소스에 접근할 수 있는 스레드의 수를 전달count Value
라고 부른다.wait
메소드는 count 가 0보다 크다면 count value를 감소시키고 바로 리턴하고, 그렇지 않으면 0보다 큰 값이 될때까지 스레드를 블로킹하며 대기한다.signal
메소드는 count value를 증가시키므로 리소스의 사용이 끝났을 때 호출한다. let sem = DispatchSemaphore(value: 1)
@IBAction func semaphore(_ sender: Any) {
value = 0
for _ in 1 ... 1000{
firstQueue.async(group: group, execute: {
self.sem.wait()
self.value += 1
self.sem.signal()
})
secondQueue.async(group: group, execute: {
self.sem.wait()
self.value += 1
self.sem.signal()
})
}
group.notify(queue: .main){
self.valueLabel.text = "\(self.value)"
}
}