티스토리 뷰

728x90

경쟁 상황 (Race Condition)

 

두개 이상의 Thread를 사용하면서, 동일한 메모리 접근 등으로 인해 발생할 수 있는 문제 

 

아래 예제를 보시죠.

var value = 777

func changeValue1() {
  sleep(2)
  value = 222
}

func changeValue2() {
  sleep(2)
  value = 0
}

queue.async {
    changeValue1()
}

queue.async {
    changeValue2()
}


print("(비동기)함수 실행값:", value)

 

print에는 뭐가 찍힐까요 ? 

 

777 이나와요.

changeValue1,2 함수가 실행되고 기다리는동안 (sleep) 

제일 아래 print 문이 실행되는거죠. 

 

 

해결방법 (3가지)

 

1.  TSan (Thread Sanitizer) : 앱 출시전 반드시 해야할 일 

    Xcode - ⌘(cmd) - ' < ' - Run - Diagnostics - Thread Sanitizer 체크 

     후에 문제 되는 부분을 찾아서 해결 합니다.

 

Product → Scheme → Edit Scheme

 

Run → Diagnostics → Thread Sanitizer 체크

 

 

NOTE

Tsan 모드로 실행하면 빌드시간이 오래걸리니 경쟁상황을 해결하면 체크해제 할것

 

 

2. Serial Queue + Sync (엄격한 *Thread-safe)

 

sync 메소드는 main thread가 아닌곳에서 사용가능 하며, 잠재적 경쟁상황을 피하는데 유용합니다.

 

예제 

let serialQueue = DispatchQueue(label: "serialQueue")

DispatchQueue.global().async {
    serialQueue.sync {  // async를 사용하게되면 _count 값을 못받아올 수도 있음. 
       _count
    }
    
    _count
}

 

Thread -Safe

- 여러 Thread가 동시에 쓰여도 안전하다.
- 데이터에 여러 Thread를 사용하여 접근해도, 한번에 한개의 Thread만 접근하도록 처리하여 경쟁상황의 문제없이 사용.

 

 

GCD 강의 - SerialSyncProject - ViewController 참고

 

 

3. 디스패치 배리어 작업 (Serial Queue + Sync 보다 효율적)

 

concurrent 큐 내의 여러개 Thread 중에서 '배리어 작업'의 경우, 

한개의 Thread만 사용해 serial(직렬)로 실행가능한 방법.

 

 

예시

더보기

 

// Person.swift

import Foundation

open class Person {
    private var firstName: String
    private var lastName: String
    
    public init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }
    
    // 이름 바꾸는 메서드
    open func changeName(firstName: String, lastName: String) {
        randomDelay(maxDuration:  0.2)
        self.firstName = firstName
        randomDelay(maxDuration:  1)
        self.lastName = lastName
    }
    
    // 이름 Get계산속성
    open var name: String {
        return "\(firstName) \(lastName)"
    }
}

 

class BarrierThreadSafePerson: Person {
    
    let newConcurrentQueue = DispatchQueue(label: "com.inflearn.person.newConcurrent", attributes: .concurrent)
    
    // 🎾 쓰기 - 동시 + 배리어(Barrier) 작업으로 설정
    override func changeName(firstName: String, lastName: String) {
        newConcurrentQueue.async(flags: .barrier) {  // flags: .barrier 사용
            super.changeName(firstName: firstName, lastName: lastName)
        }
    }

    
    // 🎾 읽기 - 동시 + 동기(sync) 작업으로 설정
    override var name: String {
        newConcurrentQueue.sync {
            return super.name
        }
    }
}

 

아래 그림을 참고하자면,

 

read(읽기) 작업들은 동시에 실행하게 두되,

write(쓰기) 작업이 진행 될때는 다른 작업을 멈추고,

쓰기작업이 완료되면 멈춰있던 작업들이 재 실행 됩니다.

 

NOTE

읽기 작업은 동시에 이루어져도 괜찮지만,
읽기/쓰기 또는 쓰기 작업이 동시에 이루어 지게 되면 Thread-safe 하지 않은 상황이 됩니다.

 

GCD 강의자료 7-3 참고  


 

교착상태 (Deadlocks)

 

한정된 자원을 2개 이상의 Thread가 서로 점유하려고 하면서 자원사용이 막히는 상태.

 

해결방법

-  Serial Queue로 해결 가능

    단, 세마포어 같은 제한된 리소스 순서가 있는건 조심해서 사용해야 함.

 

 

 


우선 순위의 뒤바뀜 (Priority Inversion)

다양한 경우에서, 작업의 Qos가 뒤바뀌어서 작업이 진행되는 경우.

 

- Serial Queue에서 높은 우선순위 작업이 낮은 우선순위의 뒤에 보내지는 경우

- 낮은 우선순위의 작업이 높은 우선순위가 필요한 자원을 잠그고 있는 경우 (세마포어 등)

- 높은 우선순위 작업이 낮은작업에 의존하는 경우 (Operation) 

 

해결방법

GCD가 우선순위를 자동으로 조정해서 해결. 

 


참고

앨런: iOS(동시성) 프로그래밍, 동기/ 비동기 처리, GCD/Operation

728x90