★KEYWORD★
프로레스 내에서 실행되는 흐름의 단위 / 멀티스레드 / 작업 동시 처리
Thread(스레드) 란?
어떠한 프로그램 내에서, 특히 프로세스(컴퓨터에서 실행되고 있는 컴퓨터 프로그램) 내에서 실행되는 흐름의 단위를 말한다. 일반적으로 한 프로그램은 하나의 스레드를 가지고 있지만, 프로그램 환경에 따라 둘 이상의 스레드를 동시에 실행할 수 있다. 이러한 실행 방식을 멀티스레드(multithread)라고 한다.
특히 게임 서버쪽에서는 멀티스레드가 중요한데, 각 플레이어의 액션, 게임 내의 이벤트, 데이터 동기화 등 다양한 작업들을 동시에 처리해야 하기 때문이다.
게임 엔진 스레드
- Update (GameLogic Thread [게임 로직을 처리하기 위함])
- 유니티 업데이트 함수 / 계속 처리될 거
- Render (Game Rendering Thread)
- 임의로 사용할 수 없음
- 실제로 그려주는 애
유니티(C#) 쓰레드 예제
다음 코드를 작성해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
using UnityEngine;
public class Test_Thread : MonoBehaviour
{
void Start()
{
Numbering(1);
Numbering(2);
}
private void Numbering(int index)
{
for (int i = 0; i < 10000; i++)
print($"Numbering {index} : {i}");
}
}
|
cs |
예상했듯이 결과는 차례대로 예쁘게 출력된다.
하지만 다음과 같이 스레드를 넣어서 실행해보자.
(스레드에 관련된 기능들은 System.Threading 네임스페이스에 포함되어 있다!)
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
|
using System.Threading;
using UnityEngine;
public class Test_Thread : MonoBehaviour
{
void Start()
{
//Numbering();
//Numbering();
ThreadStart start1 = new ThreadStart(Numbering);
Thread t1 = new Thread(start1);
ThreadStart start2 = new ThreadStart(Numbering2);
Thread t2 = new Thread(start2);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
}
private void Numbering()
{
for (int i = 0; i < 10000; i++)
print($"Numbering {i}");
print($"Numbering 종료");
}
private void Numbering2()
{
for (int i = 0; i < 10000; i++)
print($"Numbering2 {i}");
print($"Numbering2 종료");
}
}
|
cs |
이처럼 처음과 같이 순서대로 나오는 것이 아닌, 뒤죽박죽으로 결과가 출력된다.
쓰레드의 Race Condition
다음은 스레드의 Race Condition을 코드를 통해 알아보자. 자세한 이론 참고
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
46
47
48
49
50
51
|
using System.Threading;
using UnityEngine;
public class Test_RaceCondition : MonoBehaviour
{
void Start()
{
Run1();
Run2();
Run3();
Run4();
print($"sum : {sum}");
}
private uint sum;
private int count = 1000000;
private void Run1()
{
for(int i = 0; i < count; i++)
{
sum += (uint)i;
}
}
private void Run2()
{
for (int i = 0; i < count; i++)
{
sum += (uint)i;
}
}
private void Run3()
{
for (int i = 0; i < count; i++)
{
sum += (uint)i;
}
}
private void Run4()
{
for (int i = 0; i < count; i++)
{
sum += (uint)i;
}
}
}
|
cs |
Start 부분에 다음과 같이 쓰레드를 추가해보자.
1
2
3
4
5
6
7
8
9
10
11
12
|
Thread[] threads = new Thread[4];
threads[0] = new Thread(new ThreadStart(Run1));
threads[1] = new Thread(new ThreadStart(Run2));
threads[2] = new Thread(new ThreadStart(Run3));
threads[3] = new Thread(new ThreadStart(Run4));
foreach (Thread t in threads)
t.Start();
foreach (Thread t in threads)
t.Join();
|
cs |
결과 값이 처음과 다르고 실행할 때 마다도 다르게 된다.
이처럼 스레드 1, 2, 3, 4번이 서로간의 경쟁 상태(Race Condition)로 들어간 것을 확인할 수 있다.
이를 해결 할 방안은 이러하다.
1
2
3
4
5
6
7
|
public class Test_RaceCondition : MonoBehaviour
{
private Mutex mutex; // 상호 배제
void Start()
{
mutex = new Mutex();
|
cs |
다음과 같이 Mutex(상호배제) 변수를 만들어준다
이제 자신이 실행 할 때 자신을 임계의 영역으로 묶어주면 된다.
이것을 Run1(),2(),3(),4() 에다가 다 적용시킨다.
1
2
3
4
5
6
7
8
9
10
11
|
private void Run1()
{
mutex.WaitOne(); // lock
for(int i = 0; i < count; i++)
{
sum += (uint)i;
}
mutex.ReleaseMutex(); // unlock
}
|
cs |
.WaitOne()은 하나가 들어가니까 나머지에게 대기하라는 의미이다. 이것이 바로 lock이다.
그리고 실행이 끝나면 다 되었으니까 문을 열겠다는 것이 ReleaseMutex()이다. 이것은 unlock이라고 보면 된다.
그렇게 하고 실행해보면,
위의 그림[1] 과 같이 정상적으로 실행된 것을 볼 수 있다.
출처 및 참고: 강의 짱잘하시는 울 유니티 선생님 수업
HyunZzang의 프로그래밍 공간 / 함께 공부해요!!
도움이 되셨다면 "좋아요❤️" 또는 "구독👍🏻" 부탁드립니다 :)