멀티스레드에서 하나의 자원에 접근할 때 동기화 이슈가 발생할 수 있습니다. 

멀티스레드에 관한것은 아래 링크를 참고하자.

2018/07/15 - [닷넷,C#,.NET] - C# 멀티 태스킹 구현, 비동기 태스크


그래서 여러 스레드가 하나에 자원에 접근할 때 하나가 해당 자원을 점유하고 있으면 다른 스레드에서는 접근을 못하고 대기하게 해야한다.


 아래 예제는 변수 num 에 두개으 스레드가 접근하여 하나의 스레드(MethodA)는 num값을 하나 증가시키고 다른 스레드(MethodB)는 num 값을 감소시킨다.

그래서 5만번씩 증가와 감소를 하면 num의 최종값은 0이되야 한다. 하지만 실제 값은 0이 아닌 다른 값이 나올때가 많다. 매번 값은 다르다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
 
namespace T
{
    class Program
    {
        static int num = 0;
        static void MethodA()
        {
            for (int i = 0; i < 50000; i++)
            {                
                    num++;                
            }
        }
        static void MethodB()
        {
            for (int i = 0; i < 50000; i++)
            {
                num--;
            }
        }
 
        static void Main(string[] args)
        {
            Stopwatch watch = Stopwatch.StartNew(); //시간 측정 시작
            Task aTask = Task.Factory.StartNew(MethodA);
            Task bTask = Task.Factory.StartNew(MethodB);
            Task.WaitAll(new Task[] { aTask, bTask });
            watch.Stop(); //시간 측정 완료
            Console.WriteLine("소요시간 : " + 
                            watch.ElapsedMilliseconds + "s");
            Console.WriteLine("num : " + num);
        }
    }
}
cs



 그래서 이와 같은 동기화 문제를 해결하기 위해서는 lock으로 num을 접근하는 코드에 넣어서 해결할 수 있다.

 아래 예제는 동기화 이슈를 해결한 코드이다.

 물론 소요시간은 위에 예제보다 더 많이 걸리게 된다. lock이 걸리면 다른 스레드는 대기하기 때문에 시간이 오래 걸리는 것이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
 
namespace T
{
    class Program
    {
        static int num = 0;
        static object conch = new object();
        static void MethodA()
        {
            for (int i = 0; i < 50000; i++)
            {
                lock (conch)     //잠금
                {
                    num++;
                }    //풀림
            }
        }
        static void MethodB()
        {
            for (int i = 0; i < 50000; i++)
            {
                lock (conch)     //잠금
                {    
                    num--;
                }    //풀림
            }
        }
 
        static void Main(string[] args)
        {
            Stopwatch watch = Stopwatch.StartNew(); //시간 측정 시작
            Task aTask = Task.Factory.StartNew(MethodA);
            Task bTask = Task.Factory.StartNew(MethodB);
            Task.WaitAll(new Task[] { aTask, bTask });
            watch.Stop(); //시간 측정 완료
            Console.WriteLine("소요시간 : " + watch.ElapsedMilliseconds + "s");
            Console.WriteLine("num : " + num);
        }
    }
}
 
cs


 이런 동기화 이슈가 발생하는 이유는 A스레드에서 변수 num값을 읽어와서 증가시킨 다음에 다시 num변수에 값을 쓴다. A스레드가 num값을 읽어간 동안 스레드 B가 num값을 다시 읽어가면 원하지 않는 값을 쓰게 된다.

 C# 코드로는 num++ 한줄이지만 어셈블리 코드로는 여러줄이 되게 된다. 그래서 이러한 문제를 lock 하는 방법 말고 Interlocked 타입을 이용해서 값을 증가하거나 감소하는 코드를 대신해서 사용하면 이런 문제를 해결할 수 있다.

 아래 예제를 보자. 위의 예제와 같은 결과를 얻을 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
 
namespace T
{
    class Program
    {
        static int num = 0;
        static object conch = new object();
        static void MethodA()
        {
            for (int i = 0; i < 50000; i++)
            {
                Interlocked.Increment(ref num);
            }
        }
        static void MethodB()
        {
            for (int i = 0; i < 50000; i++)
            {
                Interlocked.Decrement(ref num);
            }            
        }
 
        static void Main(string[] args)
        {
            Stopwatch watch = Stopwatch.StartNew(); //시간 측정 시작
            Task aTask = Task.Factory.StartNew(MethodA);
            Task bTask = Task.Factory.StartNew(MethodB);
            Task.WaitAll(new Task[] { aTask, bTask });
            watch.Stop(); //시간 측정 완료
            Console.WriteLine("소요시간 : " + watch.ElapsedMilliseconds + "s");
            Console.WriteLine("num : " + num);
        }
    }
}
cs

Posted by 꿈만은공돌
,