본문 바로가기
Programing_Language/C#

[쓰레드 동기화] Interlocked (공유자원과 임계구역)

by neohtux 2020. 7. 18.
728x90

https://docs.microsoft.com/ko-kr/dotnet/api/system.threading.interlocked?view=netcore-3.1

 

Interlocked Class (System.Threading)

다중 스레드에서 공유하는 변수에 대한 원자 단위 연산을 제공합니다.Provides atomic operations for variables that are shared by multiple threads.

docs.microsoft.com

Remarks

이 클래스의 메서드는 스레드는 다른 스레드에서 액세스할 수 있는 변수를 업데이트 하는 동안 스케줄러 컨텍스트를 전환 하는 경우 또는 별도 프로세스에서 두 스레드가 동시에 실행 중일 때 발생할 수 있는 오류 로부터 보호할 수 있습니다. 이 클래스의 멤버는 예외를 throw 하지 않습니다.

Increment  Decrement 메서드는 변수를 증가 또는 감소 시키고 결과 값을 단일 작업에 저장 합니다. 대부분의 컴퓨터에서 변수를 증가 아닙니다는 원자성 작업으로 다음 단계를:

  1. 레지스터를 인스턴스 변수에서 값을 로드 합니다.

  2. 증가 또는 감소 값입니다.

  3. 인스턴스 변수에 값을 저장 합니다.

 

요약 : 다중 스레드에서 공유하는 변수에 대한 원자 단위 연산을 제공합니다.

 

Critical Secotion(임계 구역) 내의 공유 자원의 변수에 대한 원자성(Atomic)을 보장한다는 뜻.

DB의 트랜젝션(Transaction)의 ACID의 Atomic 과 마찬가지로 All or Nothing의 개념이다.

 

아래 코드는 공유자원인 Num을 두 개의 쓰레드 (t1, t2)가 임계구역 접근하여

10만번 ++연산 --연산의 결과를 보여준다.

static int num = 0;

        static void Thread_1()
        {
            for (int i = 0; i < 100000; ++i)
            {
                num++;
            }
                
        }
        static void Thread_2()
        {
            for (int i = 0; i < 100000; ++i)
            {
                num--;
            }
                
        }
        static void Main(string[] args)
        {

            Task t1 = new Task(Thread_1);
            Task t2 = new Task(Thread_2);

            t1.Start();
            t2.Start();
            Task.WaitAll(t1, t2);

            Console.WriteLine(num);
        }

두 개의 쓰레드가 병렬로 진행되며 자원의 원자성이 보장되지 못하여 자원의 값이 예상한 값인 0이 나오지 않음을
확인 할 수 있다.

 

그 이유는, 실제 14F442C번지에 값이 누산 레지스터에 값이 들어갈때 

-1인 상태의 값을 가지고 t1 쓰레드가 증감연산을 하는지 이미 t2가 수행한 값을 가지고 있는지

보장이 되지 않기 때문이다.

 

t1 -> num

t2 -> num 에 동시접근을 한다고 생각하면

 

t1 이 0을 가지고 있는 상태에서 증감연산을 수행하고 1을 만들었다.

t2 도 0을 가지고 감산을 수행하고 -1을 만들었다

 

최종은 1인지 -1인지 항상 쓰레드가 해당 자원을 가져다 쓰는 순서가 보장 되지 않기 때문에

공유된 자원의 최종적인 값은 무엇이 될지 보장이 되지 않는다.

 

아래는 공유자원 num의 Interlocked을 걸어 Increment (증가) Decrement(감소)

연산을 이용하여 자원 점유 접근을 제어 하였다.

 static int num = 0;

        static void Thread_1()
        {
            for (int i = 0; i < 100000; ++i)
            {
                Interlocked.Increment(ref num);
            }
                
        }
        static void Thread_2()
        {
            for (int i = 0; i < 100000; ++i)
            {
                Interlocked.Decrement(ref num);
            }
                
        }
        static void Main(string[] args)
        {

            Task t1 = new Task(Thread_1);
            Task t2 = new Task(Thread_2);

            t1.Start();
            t2.Start();
            Task.WaitAll(t1, t2);

            Console.WriteLine(num);
        }

 

쓰레드간 동기화가 이루어진 결과를 확인할 수 있다.

여기서 주의할점은 저 값이 무엇인지 확인하기 위해 내부 변수를 선언하여 값을 복사하여 확인한다면

그것은 Interlock이 되지않은 값의 복사가 비동기적으로 돌기때문 피해야한다.

 

값을 확인하기 위해서 Interlocked의 반환형은 int 타입이다. 반환된 값을 확인하는 방법을 사용하자

ex) int get_num = Interlocked.Increment(ref num);

300x250

댓글