Search

'잠금 객체'에 해당되는 글 1건

  1. 2012.02.08 Welcome to Parallel C#(12) - 귀찮으면 안 해.

Welcome to Parallel C#(12) - 귀찮으면 안 해.

C# Parallel Programming 2012. 2. 8. 09:00 Posted by 알 수 없는 사용자
- 인류 발전의 원동력.

귀.찮.다. 이 세자로 이루어진 표현이 인류의 발전을 이끌어왔다면 확대해석일까욤. 정말 죠낸죠낸쵸오낸 귀찮다 보니 사람들은 어떻게 하면 반복적인 작업을 줄일 수 있을까 하는 문제를 생각하게 됩니다. 그래서 더 나은 방법을 고안해 내고, 더 빠르고 더 간편한 시스템, 그리고 나아가서 자동화를 생각하게 됩니다. 그러다 보니, 수작업으로 이루어진 프로세스는 실수의 여지가 많은 반면에 자동화로 이루어진 프로세스에서는 그 여지가 굉장히 줄어들게 되는 부수적인 효과를 얻게 되는 것이죠.

물론 귀찮음으로 인해서 인류는 많은 퇴보도 했을 겁니다. 복잡하고 비효율적인 것도 몸에 익으면 편하게 느껴지게 됩니다. 그리고 더 깔끔한 방법으로 바꾸려 하면 머리속에서는 엄청난 저항이 일어나게 되죠. 이미 한번 구성되어서 굳어진 뇌의 회로를 다시 구성해야 한다는 그...... 귀찮음. 허허허허허허


- 락에 자동화를 도입!

지난 포스트에서 Monitor를 이용해서 락을 거는 방법을 알아봤습니다. 하지만 어떤가요? 매번 try-finally블록을 사용해서 락을 걸고 해야 한다면? 그리고 Enter는 있는데 Exit를 깜빡하게 빼먹었다면? 어머나!!!!

그래서 C#에서는 귀찮음을 싫어하는 인류의 특성을 감안해서 간편한 Syntactic Sugar를 제공합니다. lock이라는 구문이 바로 그것입니다. 하하하하 :) 그럼 간단한 예를 보죠.

using System;

using System.Threading.Tasks;

 

namespace Exam24

{

    class Program

    {

        readonly static object sync = new object();

        static readonly int count = 10000000;

        static int sum = 0;

 

        static void IncreaseByOne()

        {

            for (int i = 0; i < count; i++)

            {

                lock (sync)

                {

                    sum += 1;

                }

            }

        }

 

        static void Main(string[] args)

        {

            Task task = Task.Factory.StartNew(IncreaseByOne);

 

            for (int i = 0; i < count; i++)

            {

                lock (sync)

                {

                    sum -= 1;

                }

            }

 

            task.Wait();

            Console.WriteLine("Result = {0}", sum);

        }

    }

}

어떤가요? Monitor를 사용해서 해주던 번거로운 작업이 lock이라는 구문으로 임계영역을 감싸주는 것으로 간단하게 해결 되었습니다. 자, 그럼 lock이라는 편리한 구문 뒤에 실제로 생성되는 코드를 한번 확인해 볼까욤?

private static void Main(string[] args)

{

    Task task = Task.Factory.StartNew(new Action(Program.IncreaseByOne));

    for (int i = 0; i < count; i++)

    {

        object CS$2$0000;

        bool <>s__LockTaken1 = false;

        try

        {

            Monitor.Enter(CS$2$0000 = sync, ref <>s__LockTaken1);

            sum--;

        }

        finally

        {

            if (<>s__LockTaken1)

            {

                Monitor.Exit(CS$2$0000);

            }

        }

    }

    task.Wait();

    Console.WriteLine("Result = {0}", sum);

}

 

private static void IncreaseByOne()

{

    for (int i = 0; i < count; i++)

    {

        object CS$2$0000;

        bool <>s__LockTaken0 = false;

        try

        {

            Monitor.Enter(CS$2$0000 = sync, ref <>s__LockTaken0);

            sum++;

        }

        finally

        {

            if (<>s__LockTaken0)

            {

                Monitor.Exit(CS$2$0000);

            }

        }

    }

}

위에서 볼 수 있듯이 생성되는 코드는 Moniter를 이용한 코드와 정확하게 같은 코드를 사용하고 있습니다. 대신에 훨씬 간단하죠? 물론 lock구문을 통해서 락을 걸어줄 객체는 Monitor를 사용할 때와 동일하게 readonly static이어야 합니다.
 
락을 거는 대상이 되는 잠금 객체에 대해서 조금 덧붙이자면, 락을 걸어줄 객체는 값 형(Value 타입)이 아닌 참조 형(Reference 타입) 객체여야 합니다. 왜냐면, 값 형의 경우는 다른 변수에 값을 대입할 때 값을 복사해서 넘겨 주기 때문에 매번 메모리에 동일한 값이 새로 생겨나게 됩니다. 이렇게 되면 Enter메서드에서 받는 잠금 객체와 Exit메서드에서 받는 잠금 객체가 서로 다른 객체가 되므로 SynchronizationLockException이 발생하게 됩니다. 반면에 참조 형 객체의 경우는 동일한 객체에 대한 참조를 복사해서 넘겨주므로 항상 동일한 객체에 대해서 Enter와 Exit를 호출하게 되겠죠.

그럼~ 다음에 또 뵙죵~ :)