동시성과 병렬성은 차이가 조금 있어요. 오늘은 이 둘의 차이에 대해서 간단하게만 설명하고 넘어가도록 하겠습니다. 코어가 2개, 쓰레드가 2개 있는 cpu를 생각해 봅시다. 즉 HT가 적용이 되지 않은 cpu가 있다고 생각해 봅시다.

 

 

 여기서 core를 '물건을 생산하는 기계' 정도로 생각합시다. 그것들은 1분에 하나씩 인형을 생산한다고 합시다. 그러면 코어가 1개 있으면 1분에 하나씩만 생산을 할 겁니다. 그런데 2개 있으면 1분에 2개, 3개가 있으면 1분에 3개씩 생산할 거에요.

 

 이는 '인형을 만드는 일'을 기계가 병렬적으로 처리해서 가능한 것입니다. 그러면 경우에 따라서는, 어떠한 특정한 시점에, run 상태가 둘인 경우도 있을 거에요.

 

 

 그러면 동시성은 무엇일까요?

 

 


 동시성 하면, 항상 나오는 키워드가 상호 배제, lock, 세마포어와 같은 키워드 들이 따라나옵니다. 경쟁 상태도 따라 나옵니다. 프로그램들은 실행 부분 단위들이 있어요. 예를 들자면, 가게 시뮬레이트 시스템을 생각해 봅시다. 그러면 음식을 만드는 실행 단위가 있을 거고, 주문을 받는 단위가 있을 겁니다. 그리고 계산을 하는 단위 이렇게 3개가 있어요. 이렇게 3개로 쪼갤 수 있어요.

 

 그러면 이 가게 시스템을 java의 Thread를 이용해서 구현한다면, HT가 적용되지 않고, 코어가 1개인 경우, 3개의 Task가 동시에 실행이 되는 것처럼 보일 거에요. 즉, 우리가 볼 때에는 동시에 돌고 있는 것 같지만, run state에 있는 녀석은 1개인 셈입니다.

 

 

 그러면, 주문을 받고 처리하기 위한 순서가 있을 겁니다. 예를 들자면 이런 식입니다.

 

 

 주문을 받고, 음식을 만들고, 서빙하고, 계산하는 일련의 작업들이 있을 겁니다. 그런데, 직원이 사장 혼자인데, 이 순서대로만 한다면 어떤가요? 어떤 한 사람에 대해서 order가 지나치게 오래 걸리면 어떤가요? 주문의 일부를 듣고 음식을 어느 정도까지 만들 수 있지 않을까요?

 

 아니면, 손님 1번의 계산이 너무 오래 걸리면 어떨까요? 1번과 2번이 동시에 들어왔다고 해 봅시다. 손님 2가 라면을 주문 했다면, 냄비에 물을 올리면 되지 않을까요?

 

 

 대충 이런 식으로 작업 단위를 나눌 수 있다고 합시다. 만약에 1번 손님이 계산하는 데 너무 오래 걸렸다고 해 봅시다.

 

 

 그런데 1번 손님에 대한 일이 다 끝나고 2번 손님의 주문을 받고, 음식을 만든다고 해 봅시다. 그러면 2번 손님의 입장에서는 너무 오래 기다릴 겁니다. 그러면 이렇게 하면 어떤가요? 1번 손님을 위한 음식이 만들어 지고 있을 때, 2번 손님의 주문을 받습니다.

 

 

 이런 식으로 하면, 2번 손님이 음식을 주문하기 위해 기다리는 시간이 줄어들 거에요. o1 + o2 + o3 + m1 + m2 + m3 + c1 + c2 + c3만큼 기다렸는데, o1 + o2 + o3 + m1만 기다렸어요. 그러면 손님 2가 음식을 주문하는 데 너무 오래걸린다고 불평을 하지 않을 거에요. 코어 1개, 쓰레드 하나인 cpu인 경우, Thread를 extends한 worker를 동시에 start 시켰을 때, Runable한 상태는 여러 개가 있지만, 실제로 Run하는 쓰레드는 하나입니다. 그리고 이들이 조금씩 실행됩니다.

 

 그러면, 동시성이라는 키워드가 나왔는데 왜, race condition이 나오고, lock이 나왔을까요? 이는 실행 순서가 바뀌었을 때 문제가 될 수도 있기 때문입니다.

 

 


 간단한 문제 상황을 보여드리겠습니다.

 

 한 쓰레드는 인스턴스 a의 필드 v에 1을 더하는 것입니다. 그리고 다른 쓰레드는 인스턴스 a의 필드 v에 1을 빼는 겁니다. 그러면 크게 보자면 다음과 같은 세 sub 작업으로 나눌 수 있습니다. 그리고, 노란색 작업들이 먼저 실행이 되고, 파란색 작업이 실행된 경우에는 문제가 없습니다. 그런데, 이들의 순서가 바뀌면 어떻게 되는지 봅시다.

 

 

 일단 a.v에는 0이 저장이 되어 있다고 합시다. 그러면, 2번째 sub 작업까지 수행했을 때, v에는 0이 들어왔을 겁니다. 이 때, v는 Thread별로 독립적으로 가지고 있는 영역 (예를 들어 스택)에 할당이 되어 있다고 해 봅시다. 다음에 3번째 작업을 하면 v의 값은 1이 될 겁니다. 그런데 4번째 작업까지 하면, v의 값은 -1이 될 거에요. v가 Thread별로 독립적으로 가지고 있는 영역에 속하기 때문입니다.

 

 

 그러면 대략적으로 그림이 이렇게 그려질 텐데요. 6번째 작업을 수행한 순간, a.v에는 -1이 저장됩니다. 우리가 생각했던 값인 0이 아닌 -1입니다. 이는, 부분 Task가 어떤 순서로 실행되느냐에 따라서 a.v에 저장된 값이 달라지는 셈입니다. 이러한 상황을 제어하기 위해서 여러 가지 기법들이 있는데요. 이는 천천히 알아보도록 하겠습니다.