멀티스레드 환경에서 공유 자원에 접근 시, 동시에 접근하게 된다면 데이터의 일관성이 깨질 것이다. 따라서 한 번에 하나의 연산이 이루어져야 하는데, 이것을 가능하게 해주는 것이 lock이다. 하나의 스레드가 공유 자원에 대해 lock을 가지게 되면 모든 작업 완료 후 lock을 다시 반납한다. 그 동안 다른 스레드는 lock을 얻을 때까지 대기를 한다. 

 

 임계구역(Critical Section)은 lock을 잡은 상태로, 하나의 스레드만 진입할 수 있는 공간이다. 이 공간에 진입을 해야만 공유 자원에 대한 접근이 가능해진다. 이는 상호배제(Mutual Exclusion) 원칙을 충족한다.

 

 Lock을 얻고, 반납하는 과정에서 지연시간, 즉 오버헤드가 발생하게 된다. 따라서 Lock이 너무 많이 있으면 오버헤드 때문에 효율이 떨어진다. 그렇다고 Lock의 개수를 줄이고 임계구역의 길이를 늘리게 된다면, 다른 스레드들이 작업을 하지 못하기 때문에 효율이 떨어진다. 적당히 조절해야 한다.

 

 사실 Lock은 경쟁을 통해 얻게 되는데, 경쟁에서 밀려 Lock을 얻지 못한 스레드는 계속 멈춰있는다. 이를 기아상태라고 한다. 해결을 위해 CPU 스케줄링으로 우선순위를 부여하여 공정하게 Lock을 얻을 수 있게 하는 방법이 있다. 여러 방식이 있지만 이 글에서는 다루지 않을 것이다.

 

 Lock을 얻는 과정에서 문제가 발생할 수 있는데 교착상태(Dead Lock)이라고 하는 문제가 발생한다. 이건 예시를 들어 설명하겠다. A 프로세스는 실행 중 A라는 공유자원을 들고있고, B라는 자원이 필요한 상황이다. 그리고 B 프로세스는 B라는 공유자원을 들고 있고, A라는 자원이 필요한 상황이다. A와 B 프로세스는 서로가 필요한 자원을 들고있으며, 누구 하나가 Lock을 해제할 때까지 계속 기다리는 상황이 될 것이다. 이런 상황을 미리 방지하는 것이 좋다.

 


 

InterLocked

 위에서 언급했던 데이터의 일관성이 깨지는 부분에 관한 이야기다. 여러 개의 스레드가 동시에 하나의 공유 자원에 접근을 하여 수정하였다면, 내부적으로 어떤 과정이 일어날까? 보통 컴파일러의 작동 과정은 변수의 값을 불러오고, 값을 수정하고, 변수에 수정한 값을 넣어주는 과정을 거친다. 동작이 여러 번 나누어져 실행되기 때문에 일관성이 깨지는데, 이를 해결하는 것이 C#의 InterLocked이다. InterLocked를 사용하면 동작이 한 번에 실행되는 효과를 가진다.

 

InterLocked.Increment(ref number);
InterLocked.Decrement(ref number);

 


Lock / Monitor

lock 문은 임계구역을 직접 설정해줄 수 있다. 따라서 한 번에 하나의 스레드만 접근이 가능하다.

 

object obj = new object();

lock(obj) {

	~~~

}

 

 Monitor 클래스는 lock과 비슷한 역할을 한다. 명시적으로 락을 얻고 반납하는 것을 설정한다.

 

object obj = new object();

Monitor(obj).Enter();
~~~
Monitor(obj).Exit();

 


+ Recent posts