[iOS] 차근차근 시작하는 GCD — 12

경쟁 상태(Race Condition) 해결 방법에 대해 알아봅시다 — Serial queue + sync

naljin
8 min readApr 14, 2021

Previously on GCD…

저번 시간에는 아래의 세가지 동시성 문제 중 첫번째 경쟁 상태에 대해 알아보았어요!

  1. Race Condition (경쟁 상태)
  2. Deadlock (교착상태)
  3. Priority Inversion (우선 순위의 뒤바뀜)

경쟁 상태란 “두 개 이상의 스레드를 사용하면서, 동일한 메모리 접근 등으로 인해 발생할 수 있는 문제”로 정의 내린 후, 두 개의 스레드가 동일 메모리에 접근하여 자료의 일관성을 해치는 예시를 살펴보았습니다.

그럼 이번 시간에는 경쟁상태에 대한 해결 방안에 대해 다뤄볼건데요! 자원에 다수의 스레드가 접근해서 문제가 되는거니까 한번에 한개의 스레드만 접근 가능하도록 처리하면 되겠죠?! (Thread-safe)

경쟁 상태 확인 방법 — TSan (Thread Sanitizer)

그럼 경쟁 상황이 발생하는 코드부터 찾아봅시다!

🤔 엥 내 코드에서 경쟁 상황이 어디 발생하는지 어케 알어;; 그걸 알 정도면 첨부터 제대로 짰겠지;;

걱정 마시라요. 바로 Xcode의 기본 기능인 Thread Sanitizer을 이용하면 됩니다. 이걸 체크하고 빌드 돌리면 thread safe 하지 않은 상황이 발생할 수 있는 모든 가능성을 엑스코드에서 체크해준다구여

Product > scheme > editScheme > Run > Diagnostics > Thread Sanitizer

다만 TSan을 체크하면 빌드가 오래걸리기 때문에 확인을 마치고 나서는 다시 체크해제 하는 것을 잊지 맙시다요

튼 빌드를 돌려보면! thread safe하지 않은 코드 쪽에 이렇게 보라색으로 알림이 뜹니다!

왼쪽의 issue navigator 에서 더 자세히 확인 가능하구요? thread 3이랑 5가 경쟁적으로 데이터에 접근하고 있네요

해결 방법 (aka. ThreadSafe 하게 구현하기)

자! TSan 을 이용해 어느 부분이 ThreadSafe 하지 않은지 확인도 했으니 해결을 해봅시다.

ThreadSafe하게 코드를 작성이라… hoxy 학부때 운영체제를 들은 분이라면 “스레드? 공유자원? 동기화? 뮤텍스? 세마포어? lock?” 의 흐름이 어렴풋이 지나갔을 수도 있어여

자원에 대한 접근을 동기화하기 위해 사용하는 방법 중 하나가 뮤텍스고,, 여기서 사용하는 매커니즘이 locking 이긴 한디,,

그런데 오히려 공유 자원을 lock 으로 처리하다가 교착상황 발생할 가능성이 높다고 합니다.

🤔 엥? 왜? OS 수업에서도 배운 근-본 방법인데???

왜냐면 올바르게 구현하는 것이 어렵기 때문이죠 ㅎㅎ ㅋㅋㅋㅋㅋㅋ쿠ㅜㅜ

하…. 튼 그러한 이유로.. 지금부터 설명할 두가지 방법을 이용해서 해결해봅시다

Serial queue와 sync (엄격한 Thread-safe)

첫번째 방법은바로 공유 자원을 읽고 쓰는 작업을 모두 하나의 serial 큐로 보내서 처리하는 것입니다. 그러면 들어온 task 에 순서가 생기는, 즉 다수의 스레드에서 동시에 값을 접근하지 못하게 하는 상황이 됩니다.

여기서 또 하나 주목해야할 점은 sync 를 사용한다는 것입니다. 그 이유는 serial queue로 보낸 작업을 기다림으로써 공유 자원의 제대로 된 값을 얻기 위함입니다. 만약 위의 코드를 async 로 방법을 변경한다면 실행 흐름이 task 를 보내자마자 바로 return 이 될 것이고, 맨 마지막줄의 print 값은 mySharedValue 의 초기값인 0 이 될 수도 있습니다.

물론 main 에서 sync를 사용하면 UI가 멈출 수 있기 때문에, 메인 스레드에서는 사용하면 안됩니다. 위의 코드처럼 메인 스레드에서 떨어져 여러 스레드에서 동작(ex. DispatchQueue.global())하는 비동기 작업들에 대해 사용해야합니다.

이렇게 Serial queue + sync 의 조합을 통해 모든 쓰레드(메인 쓰레드가 아닌)가 일관된 값을 얻는 코드를 설계할 수 있습니다.

좋아여 이제 아래의 코드를 함 돌려 볼까요?!

저는 한 30번 정도 같은 코드를 돌려봤을때 아래의 다양한 결과가 나왔어여

//case 1
userInteractive queue 100
background queue 100
//case 2
background queue 200
userInteractive queue 200
//case 3
userInteractive queue 200
background queue 100

🤔 : ??????????? 머야 이거 thread safe 한거 맞아?? 값이 왜 이따ㅇ….

 : 응~ Tsan 에는 안걸려~

후 Tsan 에 안걸린다는건 Thread Safe 한 코드라는 ㅇㅒ기일텐데.. 이게 어케 된건지 살펴봅시다. 위의 코드는 아래 그림처럼 나타낼 수 있겠쥬?

그럼 이제 각 스레드에서는 serial queue 에 task 를 던져야하는디..!!!

이때 던지는 순서, 즉 serial queue에 들어가는 순서에 결과가 다르게 나온다구여!

write -> read 동작들이 연속으로 들어가는 대신, 다른 thread에서 던진 write task 가 그 사이로 들어간다면?! 덮어써진 값을 읽게 되는 것이져~~

위에서 print 된 각 케이스는 아래 같은 순서로 시리얼 큐에 들어간 상황이겠죠?

print 결과만 놓고 보자면 으엥? 이거 race condition 때랑 같은 상황 아녀?!!?라고 생각할 수 있겠지만, print 결과와는 별개로 공유되는 값에 한번에 한개의 스레드만 접근하고 있으니 Thread Safe 한 상황이라는 점!!!!!!!!!을 기억하셔야해요!!!!!! (물론 저에게 하는 말입니다 ◠‿◠ ..)

마무리

ㅎㅎㅎ… 경쟁 상태 해결하기 위한 두가지 방법 알아보기! 라고 해놓고 왜 한가지 방법만 말했는데 마무리 하냐고요..? 글이 너무 길어지는 것 같기 때문이죠..ㅎㅎ

두번째 방법인 디스패치 배리어에 대해서는 다음 시간에 알아보도록 합시다! 그럼 20000~

이전 포스팅 👈🏻

다음 포스팅 👉🏻

출처

--

--