Home Full Site
Thread-Unsafe의 예제

멀티쓰레드는 필드의 내용을 동시에 엑세스하여 잘못된 결과를 만들거나 출력할 수 있는데, 이를 Thread Unsafe 하다고 한다. 아래 예제는 여러 개의 쓰레드가 Thread-Safe하지 않은 메서드를 호출하는 예를 보여주고 있다. 10개의 쓰레드가 counter라는 필드를 동시에 쓰거나 읽는 샘플로서 한 쓰레드가 counter변수를 변경하고 읽기 전에 다른 쓰레드가 다시 counter변수를 변경할 수 있기 때문에 불확실한 결과를 출력하게 된다.

예제

using System;
using System.Threading;

namespace MultiThrdApp
{
    class MyClass
    {
        private int counter = 1000;

        public void Run()
        {
            // 10개의 쓰레드가 동일 메서드 실행
            for (int i = 0; i < 10; i++)
            {
                new Thread(UnsafeCalc).Start();    
            }
        }

        // Thread-Safe하지 않은 메서드
        private void UnsafeCalc()
        {
            // 객체 필드를 모든 쓰레드가
            // 자유롭게 변경
            counter++;

            // 가정 : 다른 복잡한 일을 한다
            for (int i = 0; i < counter; i++)
                for (int j = 0; j < counter; j++) ;

            // 필드값 읽기
            Console.WriteLine(counter);
        }
    }
}


Thread-Unsafe 실행 결과


C# lock 키워드

C#의 lock 키워드는 특정 블럭의 코드(Critical Section이라 부른다)를 한번에 하나의 쓰레드만 실행할 수 있도록 해준다. lock()의 파라미터에는 임의의 객체를 사용할 수 있는데, 주로 object 타입의 private 필드를 지정한다. 즉, private object obj = new object() 와 같이 private 필드를 생성한 후, lock(obj)와 같이 지정한다.

특히, 클래스 객체를 의미하는 this를 사용하여 lock(this)와 같이 잘못 사용할 수 있는데, 이는 의도치 않게 데드락을 발생시키거나 Lock Granularity를 떨어뜨리는 부작용을 야기할 수 있다. 즉, 불필요하게 객체 전체 범위에서 lock을 걸어 다수의 메서드가 공유 리소스를 접근하는데 Lock Granularity를 떨어뜨리는 효과가 있으며 (주: 이 경우 필드나 속성들이 잠긴다는 의미는 아니며, 이들 필드나 속성을 계속 읽고 쓸 수 있음) , 또한 만약 외부에서 해당 클래스 객체를 lock 하고 그 객체 안의 메서드를 호출한 경우 그리고 그 메서드안에서 다시 lock(this)를 하는 경우, 이미 외부에서 잡혀있는 lock이 풀리기를 메서드 내에서 계속 기다릴 것이므로 데드락이 발생할 수 있다.

그리고 Critical Section 코드 블록은 가능한 한 범위를 작게하는 것이 좋은데, 이는 필요한 부분만 Locking한다는 원칙에 따른 것이다.


예제

using System;
using System.Threading;

namespace MultiThrdApp
{
    class MyClass
    {
        private int counter = 1000;

        // lock문에 사용될 객체
        private object lockObject = new object();

        public void Run()
        {
            // 10개의 쓰레드가 동일 메서드 실행
            for (int i = 0; i < 10; i++)
            {
                new Thread(SafeCalc).Start();    
            }
        }

        // Thread-Safe 메서드
        private void SafeCalc()
        {
            // 한번에 한 쓰레드만 lock블럭 실행
            lock (lockObject)
            {
                // 필드값 변경
                counter++;

                // 가정 : 다른 복잡한 일을 한다
                for (int i = 0; i < counter; i++)
                    for (int j = 0; j < counter; j++) ;

                // 필드값 읽기
                Console.WriteLine(counter);
            }
        }

        //출력 예:
        // 1001
        // 1002
        // 1003
        // 1004
        // 1005
        // 1006
        // 1007
        // 1008
        // 1009
        // 1010
    }
}



© csharpstudy.com