멀티쓰레드 프로그래밍 이슈: 경쟁 상태, 테어링, 데드락, 거짓 공유

Jin Hur·2022년 5월 14일
0

reference: "전문가를 위한 C++" / 마크 그레고리

이슈

멀티쓰레드 프로그래밍에서의 이슈는 '경쟁 상태', '테어링', '데드락', '거짓 공유' 등과 같은 문제를 발생하지 않게 만드는 것이다. 이러한 문제들은 주로 아토믹(atomic)명시적 동기화(explicit synchronization)으로 해결한다.


경쟁 상태


source: https://junstar92.tistory.com/336

여러 쓰레드들이 공유 자원에 동시에 접근할 때 경쟁 상태가 발생할 수 있다(이 중 공유 메모리에 대한 경쟁 상태를 '데이터 경쟁'이라 부름). 데이터 경쟁은 여러 쓰레드가 공유 메모리에 동시에 접근할 수 있는 상태에서 최소 하나의 쓰레드가 그 메모리에 데이터를 쓸 때 발생한다. 문제는 쓰레드들이 수행하는 읽기 작업이나 쓰기 작업이 아토믹(atomic)하게 일어나지 않는다는 것이다. 하나 이상의 기계명령어들로 수행되며, 하나의 쓰레드가 이 기계명령어들이 수행되는 도중 문맥 교환이 되지 않을 보장이 없다. 따라서 의도치 않은 결과를 갖을 수 있다.

ex) 'Uncontrolled Scheduling 문제', https://velog.io/@jinh2352/2.-Thread-API-Mutex%EC%83%81%ED%98%B8-%EB%B0%B0%EC%A0%9C


테어링

테어링(tearing)이란 데이터 경쟁의 특수한 경우로서, 크게 '읽기 테어링(torn read)'와 '쓰기 테어링(torn write)'의 두 가지가 있습니다. 어떤 스레드가 메모리에 데이터의 일부만 쓰고 나머지 부분을 미처 쓰지 못한 상태에서 다른 스레드가 이 데이터를 읽으면 두 스레드가 보는 값이 달라집니다. 이를 '읽기 테어링'이라고 합니다. 또한 두 스레드가 이 데이터에 동시에 쓸 때 한 스레드는 그 데이터의 한쪽 부분을 쓰고, 다른 스레드 는그 데이터의 다른 부분을 썼다면 각자 수행한 결과가 달라지는데, 이를 '쓰기 테어링'이라고 합니다.


데드락

경쟁 상태를 막기위해 상호배제(뮤텍스)와 같은 기법을 적용하다 보면 멀티쓰레드 프로그래밍에서 흔히 발생하는 또 다른 문제인 데드락(교착상태)에 부딪히기 쉽다. 데드락이란 여러 쓰레드가 서로 상대방이 작업이 끝날 떄까지 동시에 기다리는 상태를 말한다.

source: https://velog.io/@jinh2352/%EC%A1%B0%EA%B1%B4%EB%B3%80%EC%88%98Condition-Variables

source: https://nsgg.tistory.com/455

데드락이 발생하지 않게 하려면 모든 쓰레드가 일정한 '순서'로 자원을 획득하도록 (순서적)동기화(synchronization) 시켜야 한다. 예를 들어 (1) '생상자-소비자 문제'에서는 생성자 쓰레드, 소비자 쓰레드들 각각을 위한 두 가지 조건변수를 두는 방식을 사용하거나, (2) 자원 접근 권한을 요청하는 작업에 시간제한을 걸어두는 방식(주어진 시간 안에 자원을 확보할 수 없으면 더 이상 기다리지 않고 현재 확보한 권한을 해제) 등이 있다. 이러한 방식들로 다른 쓰레드가 자원에 접근할 기회를 줄 수 있다.
(주어진 데드락 상황에 따라 해결할 수 있는 기법은 다양하다.)


거짓 공유

실제로 공유되지 않은 데이터이지만 캐시 구조의 특성으로 마치 공유되는 것으로 인식되어 불필요한 성능 저하 현상이 있어나는 것이다.

즉 각 소켓(프로세서) 또는 코어가 동일한 캐시 라인 내의 서로 다른 위치의 데이터를 지역 캐시를 통해 참조할 때 거짓 공유가 발생한다. 실제 같은 데이터를 참조하지 않은 상황에도 불구하고 캐시 일관성 유지 프로토콜 통신(버스 트래픽)과 공유 캐시, 메모리로의 write-back으로 인한 오버헤드가 발생한다.

거짓 공유 관련 글: https://velog.io/@jinh2352/%EA%B1%B0%EC%A7%93-%EA%B3%B5%EC%9C%A0False-Sharing

캐시 라인에 걸쳐 있지 않도록 데이터 구조가 저장될 메모리 영역을 명시적으로 정렬하면 여러 쓰레드가 접근할 때 대기하지 않게 만들 수 있다. C++에선 alignas 키워드를 사용하여 데이터가 적절히 정렬되도록 할 수 있다.

캐시 라인의 수는 주기억장치 블록의 수보다 적기 때문에 캐시 라인 배치를 위한 사상(mapping)해주는 알고리즘이 필요

(1) 직접 사상(direct mapping)



(2) 연관 사상(associative mapping)


(3) 세트 연관 사상(set-associative mapping)



https://velog.io/@jinh2352/CPU-%EC%BA%90%EC%8B%9C-%EC%82%AC%EC%83%81mapping-%ED%95%A8%EC%88%98-%EC%A0%95%EB%A6%AC

0개의 댓글