• 카테고리

    질문 & 답변
  • 세부 분야

    모바일 앱 개발

  • 해결 여부

    미해결

Barrier 작업 관련 질문입니다.

23.01.15 19:48 작성 23.01.15 20:07 수정 조회수 208

1

안녕하세요! 본강의 Barrier파트를 듣고 직접 구현을 해보았을때, 모르는 부분이 생겨 질문을 드립니다.

 

barrier 작업들을 DispatchGroup으로 묶었을 경우

Tsan을 체크하고 빌드하는 경우 Thread-Safe하지 않는 부분이 감지되었습니다.

반면, 똑같은 barrier 작업을 DispatchGroup으로 묶지않는 경우는

Tsan에 감지되지 않는데, 이런 결과가 나오는 이유를 모르겠어서 질문드립니다!

// Tsan에 감지되는 코드(DispatchGroup)

for i in 1...5 {
    print("--- \(i)번째 ---")

    let group = DispatchGroup()

    var someNumber = 10

    DispatchQueue.global().async(group: group, flags: .barrier) {
        sleep(1)
        someNumber *= 10    // Race Condition 감지되는 부분
        print("after multiple 10 : \(someNumber)")
    }

    DispatchQueue.global().async(group: group, flags: .barrier) {
        sleep(1)
        someNumber += 1     // Race Condition 감지되는 부분
        print("after add 1 : \(someNumber)")
    }

    group.notify(queue: .main) {
        print("\(i)번째 결과 : \(someNumber)")
    }

    group.wait()
}
// Tsan에 감지되지 않는 코드(DispatchGroup X)

for i in 1...5 {
    print("--- \(i)번째 ---")

    var someNumber = 10

    DispatchQueue.global().async(flags: .barrier) {
        sleep(1)
        someNumber *= 10
        print("after multiple 10 : \(someNumber)")
    }

    DispatchQueue.global().async(flags: .barrier) {
        sleep(1)
        someNumber += 1
        print("after add 1 : \(someNumber)")
    }
}

답변 1

답변을 작성해보세요.

0

안녕하세요 코코 님!

조금 잘못 실험하신 부분이 있으신 것 같은데요,
제 경우엔 위아래 코드가 다 경쟁상황에 걸립니다. 디스패치 그룹 여부와는 상관이 없습니다.


그리고, 장벽작업을 사용하시려면 디폴트큐를 사용하시면 안되고,
직접 큐를 커스텀으로 만드신 다음에 하셔야합니다.


var concurrentQueue = DispatchQueue(label: "label", attributes: .concurrent)

var someNumber = 10
        
for i in 1...5 {
     print("--- \(i)번째 ---")

     concurrentQueue.async(flags: .barrier) {
          sleep(1)
          someNumber *= 10
          print("after multiple 10 : \(someNumber)")
     }

     concurrentQueue.async(flags: .barrier) {
          sleep(1)
          someNumber += 1
          print("after add 1 : \(someNumber)")
     }
}

이런식으로 먼저 커스텀큐를 만드신 다음 장벽 작업으로 보내시면 됩니다.

그리고, 아무래도 실험을 하시려는 것이면.. someNumber 변수를 for문 내부가 아닌 바깥에 선언하시는 것이 맞을 것 같아서, 위처럼 실험을 해보시면 될 것 같네요!

잘 동작을 합니다.


감사합니다. :)

 

코코님의 프로필

코코

질문자

2023.01.16

image

customQueue로도 해보았을때 위의 사진처럼 Tsan으로 빌드하는 경우 감지되었습니다.

 

추가적으로 someNumber을 바깥으로 보냈을때도, 문제가 발생하였습니다. ㅠㅠ

(그룹이 왜 for문 안에 들어가 있는 것일까요? 저건 계속 새로운 그룹 객체를 생성하는 코드 입니다. 코드가 잘못되셨어요. 그룹도 당연히 for문 밖으로 빼셔야 겠죠....)

그럼에도 불구하고,
비동기로 보낼때, 장벽작업(barrier)과 디스패치 그룹(dispatch group)을 같이 사용했을때, 내부적으로 뭔가 잘못동작하는 것이 있는 것 같네요.

내부적으로 돌아가는 어떤 메커니즘이 있는 것들을.. 제가 다 파악하기는 힘듭니다. 저도 검색을 해봤지만, 특별하게 이런 내용에 대해서 나와있는 것도 없고, 공식문서에도 이와 관련된 내용도 기술되어 있지 않네요. (구글링해서 찾게 되시면 저도 좀 알려주세요.)

정말 원하시는 것이 디스패치 그룹을 사용하고 싶으신 것이면, 다른 방식으로 문제를 해결하면 되시지 않을까 합니다. 디스패치 그룹에 있는 enter / leave를 사용하시면 Thread-safety 문제없이 일단 원하시는 대로 동작하기는 합니다.

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        checkRace()
    }
    
    var concurrentQueue = DispatchQueue(label: "label", attributes: .concurrent)
    var someNumber = 10
    let group1 = DispatchGroup()
    
    func checkRace() {
        
        for i in 1...5 {
            print("--- \(i)번째 ---")
    
            self.group1.enter()
            concurrentQueue.async(flags: .barrier) {
                sleep(1)
                self.someNumber *= 10
                print("after multiple 10 : \(self.someNumber)")
                self.group1.leave()
            }
            
            self.group1.enter()
            concurrentQueue.async(flags: .barrier) {
                sleep(1)
                self.someNumber += 1
                print("after add 1 : \(self.someNumber)")
                self.group1.leave()
            }
        }
        
        group1.notify(queue: DispatchQueue.global()) {
            print("끝")
        }
    }
}

감사합니다.. :)