[C# 강좌 22강] 쓰레드 - 동기화

공부/C#  2012.09.29 13:55



안녕하세요 개발자 명월입니다.

 

이번 포스팅에서는 스레드의 동기화에 대해 알아보겠습니다. 앞서 포스팅에서 보았듯이 스레드는 최소단위의 독립적으로 실행되는 단위입니다. 즉 세개의 각자의 객체를 사용하기에 하나의 변수 또는 개체를 사용하면 에러가 날 수 있습니다. 예를 들어 a 라는 변수가 있는데 3개의 스레드가 동시에 그값에 변화를 주면 거의 힘들지만 우연치 않게 동시에 변화를 주면 원하는 값이 나오지 않을 수 있습니다.

 

설명보다는 예제로 확인 하곘습니다.

 

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Text;
  4 using System.Threading;
  5
  6 namespace Blog20120929
  7 {
  8     class Program
  9     {
10         static void Main(string[] args)
11         {
12             new Program();
13         }
14         int a = 10000;
15         Thread _thread1, _thread2, _thread3;
16         public Program() {
17             _thread1 = new Thread(new ThreadStart(Thread_Method1));
18             _thread1.Start();
19
20             _thread2 = new Thread(new ThreadStart(Thread_Method2));
21             _thread2.Start();
22
23             _thread3 = new Thread(new ThreadStart(Thread_Method3));
24             _thread3.Start();
25
26             Timer tm = new Timer(new TimerCallback(Thread_Print), null, 0, 1000);
27             Console.ReadLine();
28         }
29         public void Thread_Method1() {
30             int i = 0;
31             while (this.a > 0)
32             {
33                 a--;
34                 i++;
35                 Thread.Sleep(1);
36             }
37             if (a <= 0) {
38                 Console.WriteLine("Thread1 "+i.ToString());
39             }
40         }
41         public void Thread_Method2()
42         {
43             int i = 0;
44             while (this.a > 0)
45             {
46                 a--;
47                 i++;
48                 Thread.Sleep(1);
49             }
50             if (a <= 0) {
51                 Console.WriteLine("Thread2 "+i.ToString());
52             }
53         }
54         public void Thread_Method3()
55         {
56             int i = 0;
57             while (this.a > 0)
58             {
59                 a--;
60                 i++;
61                 Thread.Sleep(1);
62             }
63             if (a <= 0) {
64                 Console.WriteLine("Thread3 "+i.ToString());
65             }
66         }
67         public void Thread_Print(object obj) {
68             Console.WriteLine(a.ToString());
69         }
70     }
71 }
72

 

 

위에 스레드를 0.001초당 a를 10000에서 감소 시킵니다. 감소시킨만큼 i를 증가 시키니

값은 당연히 3333,3333,3334 정도로 나온다는 것을 예상할 수 있습니다.

 

 

그러나 결과 값은 셋 합산 값이 10000이 넘는 값을 나오는 것을 확인 할 수 있습니다. 이것은 즉 스레드가 돌면서

a의 감산이 b에서는 감산한 것이 적용되지 않았던것을 예상할 수 있습니다. 즉 동기화기 이루어 지지 않은 것이지요.

 

이런 동기화를 이루기 위해서는 C#에서는 총 3가지의 방법을 제시하고 있습니다.

 

첫번째는 lock 입니다. lock은 블록으로 설정을 하여 접근을 허락하게 됩니다. 즉 lock이 세개가 되면 그 세개가 한개의 스레트 처럼 설정하게 됩니다.

 

 

 

분명 lock 걸어 놓으니 처리 속도가 눈의 띄게 느려졌네요. 그래도 원하는 값을 받아 내진 못했지만 10000이라는 값을 넘어서지는 않았습니다.

다음은 Moniter 클래스를 이용한 동기화입니다.

 

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Text;
  4 using System.Threading;
  5
  6 namespace Blog20120929
  7 {
  8     class Program
  9     {
10         static void Main(string[] args)
11         {
12             new Program();
13         }
14         int a = 10000;
15         Thread _thread1, _thread2, _thread3;
16         public Program() {
17             _thread1 = new Thread(new ThreadStart(Thread_Method1));
18             _thread1.Start();
19
20             _thread2 = new Thread(new ThreadStart(Thread_Method2));
21             _thread2.Start();
22
23             _thread3 = new Thread(new ThreadStart(Thread_Method3));
24             _thread3.Start();
25
26             Timer tm = new Timer(new TimerCallback(Thread_Print), null, 0, 1000);
27             Console.ReadLine();
28         }
29         private Object obj = new Object();
30         public void Thread_Method1() {
31             int i = 0;
32             while (this.a > 0)
33             {
34                 Monitor.Enter(obj);
35                 try
36                 {
37                     if (this.a > 0)
38                     {
39                         a--;
40                         i++;
41                     }
42                     Thread.Sleep(1);
43                 }
44                 catch (Exception)
45                 {
46
47                 }
48                 finally {
49                     Monitor.Exit(obj);
50                 }
51             }
52             if (a <= 0) {
53                 Console.WriteLine("Thread1 "+i.ToString());
54             }
55         }
56         public void Thread_Method2()
57         {
58             int i = 0;
59             while (this.a > 0)
60             {
61                 Monitor.Enter(obj);
62                 try
63                 {
64                     if (this.a > 0)
65                     {
66                         a--;
67                         i++;
68                     }
69                     Thread.Sleep(1);
70                 }
71                 catch (Exception)
72                 {
73
74                 }
75                 finally
76                 {
77                     Monitor.Exit(obj);
78                 }
79             }
80             if (a <= 0) {
81                 Console.WriteLine("Thread2 "+i.ToString());
82             }
83         }
84         public void Thread_Method3()
85         {
86             int i = 0;
87             while (this.a > 0)
88             {
89                 Monitor.Enter(obj);
90                 try
91                 {
92                     if (this.a > 0)
93                     {
94                         a--;
95                         i++;
96                     }
97                     Thread.Sleep(1);
98                 }
99                 catch (Exception)
100                 {
101
102                 }
103                 finally
104                 {
105                     Monitor.Exit(obj);
106                 }
107             }
108             if (a <= 0) {
109                 Console.WriteLine("Thread3 "+i.ToString());
110             }
111         }
112         public void Thread_Print(object obj) {
113             Console.WriteLine(a.ToString());
114         }
115     }
116 }
117

 

 

 

lock 보다더 더 느려 졌지만 우리가 원하는 값인 3333 과는 얼추 비슷해 졌습니다.

마지막으로 Mutex를 이용한 동기화처리입니다.

사용법은 Moniter 의 흡사합니다.

 

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Text;
  4 using System.Threading;
  5
  6 namespace Blog20120929
  7 {
  8     class Program
  9     {
10         static void Main(string[] args)
11         {
12             new Program();
13         }
14         int a = 10000;
15         Thread _thread1, _thread2, _thread3;
16         Mutex mtx = new Mutex();
17         public Program() {
18             _thread1 = new Thread(new ThreadStart(Thread_Method1));
19             _thread1.Start();
20
21             _thread2 = new Thread(new ThreadStart(Thread_Method2));
22             _thread2.Start();
23
24             _thread3 = new Thread(new ThreadStart(Thread_Method3));
25             _thread3.Start();
26
27             Timer tm = new Timer(new TimerCallback(Thread_Print), null, 0, 1000);
28             Console.ReadLine();
29         }
30         private Object obj = new Object();
31         public void Thread_Method1() {
32             int i = 0;
33             while (this.a > 0)
34             {
35                 mtx.WaitOne();
36                 try
37                 {
38                     if (this.a > 0)
39                     {
40                         a--;
41                         i++;
42                     }
43                     Thread.Sleep(1);
44                 }
45                 catch (Exception)
46                 {
47
48                 }
49                 finally {
50                     mtx.ReleaseMutex();
51                 }
52             }
53             if (a <= 0) {
54                 Console.WriteLine("Thread1 "+i.ToString());
55             }
56         }
57         public void Thread_Method2()
58         {
59             int i = 0;
60             while (this.a > 0)
61             {
62                 mtx.WaitOne();
63                 try
64                 {
65                     if (this.a > 0)
66                     {
67                         a--;
68                         i++;
69                     }
70                     Thread.Sleep(1);
71                 }
72                 catch (Exception)
73                 {
74
75                 }
76                 finally
77                 {
78                     mtx.ReleaseMutex();
79                 }
80             }
81             if (a <= 0) {
82                 Console.WriteLine("Thread2 "+i.ToString());
83             }
84         }
85         public void Thread_Method3()
86         {
87             int i = 0;
88             while (this.a > 0)
89             {
90                 mtx.WaitOne();
91                 try
92                 {
93                     if (this.a > 0)
94                     {
95                         a--;
96                         i++;
97                     }
98                     Thread.Sleep(1);
99                 }
100                 catch (Exception)
101                 {
102
103                 }
104                 finally
105                 {
106                     mtx.ReleaseMutex();
107                 }
108             }
109             if (a <= 0) {
110                 Console.WriteLine("Thread3 "+i.ToString());
111             }
112         }
113         public void Thread_Print(object obj) {
114             Console.WriteLine(a.ToString());
115         }
116     }
117 }
118

 

 

Mutext 클래스를 선언하고 사용합니다.

 

 

사용법이 아주 흡사하나 결과는 Moniter 보단 조금 못합니다.

 

 

결과는 Miniter가 원하는 정답에 가장 가까웠습니다.

그러나 실제로 동기화를 하면 Moniter,Mutex 보다는 lock을 더 많이 사용하는 편입니다. 일단 구역으로 나누기에 처리가 계산이 쉽고 빠를 뿐아니라 실제적으로 0.0001초 단위의 처리를 요하는 프로그램이 그리 많치 않기때문에 lock을 사용하는 편입니다.

 


댓글 1개가 달렸습니다.
댓글쓰기
  1. 김영수
    2013.09.04 09:19 신고 |  수정/삭제  댓글쓰기

    안녕하세요 강좌잘보고있는사람입니다 ^^
    mutex클래스 사용하여 동기화 시키는 소스에
    private object obj = new object();
    가있는데 mutex를 사용하려면 꼭써줘야하나요?