ThreadPool은 무엇일까? 또 어디에 사용하면 좋을까?
여러 thread를 사용하며 프로그램을 동작시키는 경우 리소스 관리로 인한 문제가 발생할 수 있다. 여기서 리턴 값을 받지 않아도 되는 동작이 있다면 ThreadPool이라는 것을 사용하며 효율적으로 리소스를 관리할 수 있다. ThreadPool은 기존에 있는 thread에 할당하여 사용하는 방법이다. ThreadPool은 새로운 thread가 아닌 기존에 존재하는 thread를 재사용하여 효율적으로 리소스를 관리할 수 있다. 리턴 값을 받을 필요가 없는 예시로는 소프트웨어 동작 로그가 있다.
문제의 정의
하나의 객체에서 어떤 값을 파일로 저장한다. 이때 여러 thread에서 이 객체의 파일 저장을 수행할 수 있다. A라는 동작 이후 저장할 수도 있고 B라는 동작 이후 저장할 수도 있다. 이때 A 동작 후 파일 쓰기를 수행 중인데 B 동작으로 인한 파일 쓰기가 실행될 때 예외가 발생한다. 이를 ThreadPool을 사용하면 해결할 수 있다.
문제가 발생되는 코드
WriteLog라는 클래스를 통해 하나의 객체를 생성하였다. 먼저 객체에 파일 이름과 데이터를 저장한다. 하나의 thread에 이 객체의 파일 저장 동작을 할당하였다.(위의 예시 중 동작 A에 해당)
이후 다른 파일 이름과 데이터를 객체에 저장한다. 이 값을 파일로 출력하기 위해 파일 저장 동작을 수행하였다. (위의 예시 중 동작 B에 해당)
코드는 아래와 같으며 2번째 파일 쓰기 중 예외가 발생하는 것을 확인할 수 있다.
- WriteLog.cs
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;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.IO;
namespace threadPoolTest
{
public class WriteLog
{
public string WritePath = "";
public List<string> LogStrList = null;
public WriteLog()
{
LogStrList = new List<string>();
}
public void Write()
{
if (WritePath == "" || LogStrList == null)
{
return;
}
using (StreamWriter writer = new StreamWriter(WritePath))
{
foreach (var line in LogStrList)
{
writer.WriteLine(line);
}
LogStrList = new List<string>();
}
}
}
}
|
cs |
- Program.cs
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace threadPoolTest
{
class Program
{
static WriteLog writeLog = null;
static void Main(string[] args)
{
writeLog = new WriteLog();
writeLog.WritePath = "test1.txt";
SetLogData1();
Thread thread = new Thread(writeLog.Write);
thread.Start(); // writeLog.Write();
writeLog.WritePath = "test2.txt";
SetLogData2();
writeLog.Write();
}
static void SetLogData1()
{
if (writeLog == null)
{
return;
}
int numOfRand = 1000000;
Random rand = new Random();
int randNum = 0;
for (int i = 0; i < numOfRand; i++)
{
randNum = rand.Next(numOfRand);
writeLog.LogStrList.Add(String.Format("randNum[idx] : {0}[{1}]", randNum, i));
}
}
static void SetLogData2()
{
if (writeLog == null)
{
return;
}
int numOfRand = 10;
Random rand = new Random();
int randNum = 0;
for (int i = 0; i < numOfRand; i++)
{
randNum = rand.Next(numOfRand);
writeLog.LogStrList.Add(String.Format("randNum[idx] : {0}[{1}]", randNum, i));
}
}
}
}
|
cs |
문제를 해결하기 위한 ThreadPool 코드
이를 해결하기 위해 ThreadPool을 사용하자. ThreadPool은 System.Threading 네임스페이스에서 사용할 수 있다. WriteLog 클래스에서 쓰기 동작에 해당하는 Write 함수를 변경한다. private로 wrtie 함수를 만들어 원래의 Write 함수에는 ThreadPool.QueueUserWorkItem(write)를 작성한다. ThreadPool에 Queue로 동작들을 할당하여 쓰기 동작을 순차적으로 하나의 thread에서 처리할 수 있게 해주는 방식이다. 코드는 아래와 같다.
- WriteLog.cs
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.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.IO;
namespace threadPoolTest
{
public class WriteLog
{
public string WritePath = "";
public List<string> LogStrList = null;
public WriteLog()
{
LogStrList = new List<string>();
}
public void Write()
{
ThreadPool.QueueUserWorkItem(write);
}
private void write(object obj)
{
if (WritePath == "" || LogStrList == null)
{
return;
}
using (StreamWriter writer = new StreamWriter(WritePath))
{
foreach (var line in LogStrList)
{
writer.WriteLine(line);
}
LogStrList = new List<string>();
}
}
}
}
|
cs |
Reference
- https://www.csharpstudy.com/Threads/threadpool.aspx
- https://github.com/daegukdo/TIL/tree/master/02_C_sharp/022_ThreadPoolEx
- https://mongyang.tistory.com/118
- https://nowonbun.tistory.com/136
- https://podo1017.tistory.com/163
- https://kdsoft-zeros.tistory.com/12
'Study > C#' 카테고리의 다른 글
const보다는 readonly 사용이 권장되는 이유 (0) | 2021.07.27 |
---|---|
지역변수를 선언할 때는 var를 사용을 권장하는 이유 (0) | 2021.07.19 |
WPF multi-thread (dispatcher, background worker) (0) | 2021.04.14 |
C#에서 화면의 특정 부분 capture하기 (0) | 2021.04.08 |
C#에서 kd-tree 사용하기 (3차원, 2차원 상의 특정 점에서 가장 가까운 점 찾기) (0) | 2021.02.28 |