#26. [모던 C++ STL] condition_variable(C++11)
- (C++11~) condition_variable이 추가되어 쓰레드간 동기화를 위해 쓰레드를
wait()
시킨뒤, 특정 조건이 되었을때 활성화 시킬 수 있습니다.
쓰레드 동기화
쓰레드를 동시에 실행시키는 것도 필요하지만, 상황에 따라서 쓰레드를 순차적으로 실행하는 쓰레드 동기화를 하는 것도 필요합니다.
예를 들어
- 인터넷에서 파일을 다운로드 받는 쓰레드가 종료되었을때
- UI 쓰레드에서 다운로드 완료 버튼이 나와야 합니다.
STL 에서는 쓰레드 동기화를 위해 condition_variable를 사용할 수 있습니다.
항목 | 내용 |
---|---|
condition_variable (C++11~) | 쓰레드간 동기화를 위해 쓰레드를 wait() 시킨뒤, 특정 조건이 되었을때 활성화 시켜 주는 개체입니다. |
condition_variable_any (C++11~) |
(작성중) |
notify_all_at_thread_exit() (C++11~) |
(작성중) |
cv_status (C++11~) |
no_timeout , timeout 이 정의된 열거형입니다. |
condition_variable
쓰레드가 종료된 시점을 알기 위해 매 시간마다 쓰레드의 상태를 검사하는 방법은 CPU 자원을 많이 사용하기 때문에 좋지 않습니다.
멀티 쓰레드 프로그램에서는 이런 경우 생산자(Producer)-소비자(Consumer) 패턴을 많이 이용하는데요,
이때
- 생산자(Producer)는 각 작업을 생성 및 관리하고,
- 소비자(Consumer)는 각 작업을 수행합니다.
- 소비자는 작업을 대기(Wait)하고 있다가, 생산자가 작업하라 알려주면(Notify), 그제서야 작업을 시작합니다.
즉, 생산자의 데이터 생성이 완료되면, 소비자가 데이터를 사용해야 하므로, 생산자와 소비자의 작업은 동기화되어야 합니다.
condition_variable은 쓰레드간 동기화를 위해 쓰레드를 wait()
시킨뒤, 특정 조건이 되었을때 notify_one()
이나 notify_all()
로 활성화 시켜 주는 개체입니다.
condition_variable의 멤버 함수는 다음과 같습니다.
항목 | 내용 |
---|---|
notify_one() (C++11~) |
대기중인 한개의 쓰레드에게 알립니다. |
notify_all() (C++11~) |
대기중인 모든 쓰레드에게 알립니다. |
wait() (C++11~) |
Notify가 왔을때 주어진 조건자가 참이면 대기를 해제합니다. |
wait_for() (C++11~) |
Notify가 왔을때 주어진 조건자가 참이거나 주어진 기간이 지나면 대기를 해제합니다. |
wait_until() (C++11~) |
Notify가 왔을때 주어진 조건자가 참이거나 주어진 시간이 되면 대기를 해제합니다. |
native_handle() (C++11~) |
시스템에 따른 구현에 정의된 핸들을 리턴합니다. |
예를 들어 다음의 wait()
는
- 누군가가
m_IsRun1
을true
로 설정하고, m_CV.notify_one()
또는m_CV.notify_all()
을 호출할때 까지 대기합니다.
1
2
3
4
std::condition_variable m_CV;
std::unique_lock<std::mutex> lock{mutex};
m_CV.wait(lock, [&]() -> bool {return m_IsRun1;});
다음은 condition_variable 사용 예입니다.
Build()
-> Part1()
-> Build()
-> Part2()
-> Build()
-> Part1()
-> Build()
의 순서로 동기화 하여 실행하고 있습니다.
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
class A {
std::string m_Str;
std::condition_variable m_CV;
bool m_IsRunBuild{false}; // true면 Build가 wait를 멈춥니다.
bool m_IsRun1{false}; // true면 Part1이 wait를 멈춥니다.
bool m_IsRun2{false}; // true면 Part2가 wait를 멈춥니다.
public:
const std::string& GetStr() const {return m_Str;}
void Part1(std::mutex& mutex) {
std::unique_lock<std::mutex> lock{mutex};
m_CV.wait(lock, [&]() -> bool {return m_IsRun1;}); // 누군가가 m_IsRun1 == true로 하고 m_CV에서 notify할때까지 대기
std::cout << "#2. Part1()" << std::endl;
m_Str += "Hello";
m_IsRunBuild = true; // Build를 깨움
m_IsRun1 = false;
lock.unlock();
m_CV.notify_all();
// 아직 할일이 더 있어 wait
lock.lock();
m_CV.wait(lock, [&]() -> bool {return m_IsRun1;}); // 누군가가 m_IsRun1 == true로 하고 m_CV에서 notify할때까지 대기
std::cout << "#6. Part1()" << std::endl;
m_Str += "!!";
m_IsRunBuild = true; // Build를 깨움
m_IsRun1 = false;
lock.unlock();
m_CV.notify_all();
}
void Part2(std::mutex& mutex) {
std::unique_lock<std::mutex> lock{mutex};
m_CV.wait(lock, [&]() -> bool {return m_IsRun2;});// 누군가가 m_IsRun2 == true로 하고 m_CV에서 notify할때까지 대기
std::cout << "#4. Part2()" << std::endl;
m_Str += "World";
m_IsRunBuild = true; // Build를 깨움
m_IsRun2 = false;
lock.unlock();
m_CV.notify_all();
}
void Build() {
std::mutex mutex; // mutex 개체
std::thread worker1{std::mem_fn(&A::Part1), std::ref(*this), std::ref(mutex)};
std::thread worker2{std::mem_fn(&A::Part2), std::ref(*this), std::ref(mutex)};
std::cout << "#1. Build Start" << std::endl;
m_IsRun1 = true; // Part1을 깨움
m_CV.notify_all();
std::unique_lock<std::mutex> lock{mutex};
m_CV.wait(lock, [&]() -> bool {return m_IsRunBuild;});
std::cout << "#3. Build Wake Up" << std::endl;
m_IsRunBuild = false;
m_IsRun1 = false;
m_IsRun2 = true; // Part2를 깨움
lock.unlock();
m_CV.notify_all();
lock.lock();
m_CV.wait(lock, [&]() -> bool {return m_IsRunBuild;});
std::cout << "#5. Build Wake Up" << std::endl;
m_IsRunBuild = false;
m_IsRun1 = true; // Part1를 깨움
m_IsRun2 = false;
lock.unlock();
m_CV.notify_all();
lock.lock();
m_CV.wait(lock, [&]() -> bool {return m_IsRunBuild;});
std::cout << "#7. Build Completed" << std::endl;
worker1.join();
worker2.join();
}
};
A a{};
a.Build();
std::cout << "Make String : " << a.GetStr() << std::endl; // HelloWorld!!를 출력함
실행 결과는 다음과 같습니다.
1
2
3
4
5
6
7
8
#1. Build Start
#2. Part1()
#3. Build Wake Up
#4. Part2()
#5. Build Wake Up
#6. Part1()
#7. Build Completed
Make String : HelloWorld!!
댓글남기기