경쟁 상태의 2가지 패턴. Check-Then-Act, Read-Modify-Write
경쟁상태란?
경쟁상태는 멀티쓰레드 환경에서 여러 쓰레드들이 공유자원(Shared Resource) 을 병행적으로 읽거나 쓰는 동작을 할때 타이밍이나 접근순서에 따라 실행결과가 달라지는 상황을 의미합니다.
경쟁상태의 대표유형
그런데 경쟁상태가 언제, 어떠한 상황에서 발생하는 것인지 잘 정리되지 않아서 이번 기회에 포스팅을 다루면서 학습을 진행해보고자 합니다.
경쟁상태의 대표적인 상황으로는 Read-Modify-Write 패턴과 Check-then-act 패턴이 있습니다. 이들에 대해 알아봅시다.
Read-Modify-Write 패턴
Read-Modify-Write 패턴이란 이전 상태를 기준으로 객체의 현재 상태를 변경하면서 발생하는 문제를 뜻합니다.
count라는 공유자원이 있습니다. 이 공유자원에 대한 연산인 count++; 연산의 경우 코드상에서는 딱 1줄만 작성되어 있지만, 사실은 3번의 연산이 일어나는 것입니다. 1) count 변수에 있는 값을 읽어오고, 2) 변수에 있는 값을 수정하고, 3) 그 변수에 있는 값을 덮어쓰는 3번의 연산이 일어나게 되는 것이죠.
쓰레드 1이 count 값 100 에서 101로 증가시키고 저장하기 직전에, 쓰레드2가 증가되기 직전의 값인 100을 읽어와서 증가시킨다면 count 의 결과값이 102가 아닌 101이 되므로 경쟁상태가 발생한 것을 알 수 있습니다.
즉, count++; 연산은 원자성(atomic) 이 보장되지 않은 연산입니다. 위 3개의 순차적인 연산 사이에서 다른 쓰레드가 중간에 개입해버리는 경우 기대하지 못한 결과가 발생하는 것이 바로 Read-Modify-Write 연산입니다.
Check-Then-Act 패턴
Check-Then-Act 패턴은 이전에 검증(Check) 한 결과가 행동(Act) 시점에는 더 이상 유효하지 않을때 발생하는 문제를 뜻합니다.
수강신청에 관한 로직이 있다고 해봅시다. 수강신청 인원이 20명 미만일 경우 폐강될수도 있기 때문에, 이와 관련한 알림 메시지 구문을 출력하는 로직입니다. 현 수강신청 인원은 count 변수로 측정하며, if문으로 20명 이하인지 체크하고 인원수에 따라 알림 구문를 보내는 것입니다.
if문(Check)을 통과하기 전에는 조건에 부합하는 값이지만, if문을 통과한 이후에는 조건에 부합하지 않는 숫자기 되기 때문에 이렇게 경쟁상태가 발생하게 되는 것입니다.
count 변수값이 15인 경우를 생각해봅시다. 이 15라는 값은 20보다 작기 때문에 if문을 통과(Check)하겠죠? 그러고 1 mills 시간동안 해당 쓰레드는 잠시 sleep 모드에 들어가게 됩니다.
그러고 이제 알림 메시지를 출력하려는데(then Act), 잠시 sleep 모드에 들어간 사이에 그 짧은 시간 찰나에 if문을 통과한 다른 여러 쓰레드들에 의해 count 값이 대폭 증가해있는 상태가 되는것입니다. 그래서 해당 쓰레드는 대폭 증가한 count 값을 출력하게되는 문제가 발생하는 것이죠.
쉽게말해, Check절 (if문) 조건에 부합하여 통과하게 되었지만, 공유자원(count 변수) 를 천천히 출력해보고자 했으나 짧은 찰나에 조건을 통과한 다른 여러 쓰레드들에 의해 조건에 부합하지 않는 숫자가 출려되는 경쟁상태가 발생하게 되는 것입니다.
결국 연산사이의 시간텀이 발생하기 때문에 한 쓰레드가 연산을 하고있을때 도중에 다른 쓰레드의 연산이 개입할 수 있어서 경쟁조건이 발생하게 되는 것입니다.