임계 영역이 무엇일까요? 이것은 둘 이상의 쓰레드 (worker)가 동시에 접근하면 안 되는, 공유 영역에 접근하는 코드를 말합니다. 다시 말해, 둘 이상의 worker가 동시에 접근하면 문제를 일으킬 수 있는 코드들을 말합니다. 예제 하나를 보면서, 같이 분석해 보도록 하겠습니다.

 

 


 먼저 ACCOUNT 클래스입니다. remain이라는 필드가 있어요. 이것은 남은 돈을 의미합니다. 이 클래스에는, put_money, 그러니까 입금하는 메서드와 get_money, 출금하는 메서드가 있어요. 이들은 각각 x만큼 넣고 빼는 일을 합니다.

 

 

 그리고 우리는 Worker의 w1과, Worker2의 w2를 동시에 실행합니다. 이들은 start 메소드가 있는 걸로 보아서는 Thread를 상속했을 거 같네요. run() 메소드만 보겠습니다.

 

 

 Worker 클래스는 1만번 500씩 넣는 연산을 수행합니다.

 

 

 Worker2는 1만번 500씩 빼는 연산을 수행합니다. 그러면, 프로그램이 다 수행이 끝났을 때, 출력값은 0이 나와야 할 겁니다. 500씩 1만번을 넣었고, 500씩 1만번을 빼 버렸기 때문입니다.

 

 

 그런데 실제 결과는 -89000이 나옵니다. 왜 그럴까요?

 

 


 ACCOUNT.class를 보겠습니다. put_money 메서드를 봅시다.

 

 

 그러면, remain = remain + x; 가 여러개의 명령어로 되어 있는 것을 볼 수 있습니다. 이를 간단하게 도식화를 시켜 봅시다.

 

 

 그러면, get_money는 어떻게 동작할까요? 당연하게도 3번만 다를 겁니다. iadd 대신에 isub가 들어가겠네요. 그러면, get_money의 1, 2, 3, 4가 수행되고, put_money의 1, 2, 3, 4가 수행되면 크게 문제가 되지는 않을 겁니다. 그런데, 이렇게 실행이 되는 경우를 생각해 봅시다.

 

 

 Worker1의 PUT 메서드가 실행이 되는 중간에, Worker2의 GET이 실행이 될 수도 있어요. 중간에. 그럴 만 합니다. 왜냐하면, w1과 w2는 block 상태에 들어가지 않고, Runnable인 상태이기 때문입니다. 먼저 ACCOUNT.remain이 0인 상태였습니다. 그러면, PUT3까지 끝냈을 때 상황을 그려보도록 하겠습니다. 500원을 넣는 작업을 한다고 생각해 봅시다.

 

 

 그러면 PUT3까지 끝났을 때, 어딘가에는 500이 들어가 있을 겁니다. 이 상황에서 다른 쓰레드의 GET 메서드가 실행됩니다. 계좌에서 500원을 빼는 연산을 합니다.

 

 

그러면 이 상황에서, GET4가 수행되었을 때, ACCOUNT.remain에는 -500이 들어갈 겁니다.

 

 

 그런데 아직 PUT4가 끝나지 않았습니다. PUT을 수행하는 쓰레드의 어딘가에는 500이 저장되어 있는데요. 이 값을 remain에 다시 씁니다.

 

 

 그러면 의도된 0과는 다른 500이 remain에 write가 되게 됩니다. 아마 데이터베이스 교과서에서 ACID를 설명하는 단원이나 locking 단원에서도 이 부분에 대해서는 언급이 되어 있을 겁니다.

 

 


 그러면 '그 어딘가' 영역을 get 쓰레드와 put을 수행하는 쓰레드가 share 한다면 어떨까요? 이 때에도 문제가 됩니다. GET과 PUT을 수행하기 위해서, '그 어딘가'는 2개가 필요합니다. remain을 저장할 공간. 그리고 x를 저장할 공간. 그러면 이런 경우를 생각해 봅시다. PUT method를 호출해서 remain과 x를 어딘가에 load를 했습니다. 먼저 500을 넣었다고 생각해 봅시다. 그런데 이 때 GET이 실행되었습니다. 그러면 어떻게 될까요?

 

 

 여기까지 실행된 상황입니다. 그런데, 이 때 GET 200이 실행된다고 해 봅시다. 그러면 local varible의 x 값이 200일 겁니다. PUT 2번까지 수행이 된다면, 노란색으로 칠한 영역이 200으로 write가 됩니다.

 

 

 이 상황에서 GET3 ~ GET4까지 수행되었다고 생각해 봅시다. 그러면, isub 때문에 임시 공간에는 -200이 들어갈 거고, 이 값을 변수 remain에 저장하기 때문에 -200이 들어갈 겁니다. 다음에 PUT3 ~ PUT4가 수행된다고 생각해 봅시다. iadd는, 노란색으로 표시된 reg와 그 오른쪽에 있는 것을 더해서, reg에 넣는 명령어라고 생각하면..

 

 노란색으로 칠한 부분의 우측의 값은 변하지 않습니다. 그렇기 때문에..

 

 

 reg라고 표시된 값은 0으로 업데이트가 되고, 그 값이 그대로 쓰여지기 때문에 remain에는 의도와 다른 값인 0이 들어가게 됩니다. 이 예제에서는 두 쓰레드가 동시에 Remain 변수에 Write 연산을 수행할 때, 어떠한 순서로 수행하느냐에 따라서 결과 값이 달라지기 때문에 문제가 생깁니다.

 

 

 이것을 어떻게 해결하면 좋을까요? put_money와 get_money를 동기화 시키면 됩니다.