반응형
스레드를 순서대로 동기화시키는 방법에 관해 다룬다.
4. Waiting for Event or Other Condition
하나의 thread가 끝낼 때 까지 다음 thread가 기다리는 것은 conditional varaible로 쉽게 가능하다.
4.1 Waiting for a Condition with Conditional Variable
- busy wait 방식 (+ sleep_for )
만약 conditional variable이 없다면 busy waiting 방식을 사용해야 한다. CPU 자원도 낭비되고, 기다려야 하는 초를 계산하기 힘들다. - Conditional Variable (C++11)
위의 문제점을 해결하기 위해 C++11부터 Conditional Variable 도입됐다. CV를 활용하면, Thread가 해당 상태가 될 때까지 Wait, 어떤 상태가 됐을 때 Notify 할 수 있다. 두 가지 구현방식이 있다.
bool flag;
std::mutex m;
void waitForFlag() {
std::unique_lock<std::mutex> lk(m);
while (!flag) {
lk.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
lk.lock();
}
}
#include
std::condition_variable;
std::condition_variable_any; // 다른 변수도 사용가능
conditional_variable은 mutex와 쓰이고, conditional_variable_any는 다른 변수로도 사용 가능하다. conditional variable이 더 좋은 성능을 낸다.
- notify_one, notify_all
- wait()
- wait() 함수는 조건을 확인할 때 항상 mutex를 잠금 상태로 유지한다.
- spurious wake (스퓨리어스,가짜 일어나기)
wait이 끝나지 않았는데 wait이 끝나서 다음 것 실행되는 상황. 조건 변수가 신호를 보낸 후, 해당 스레드가 실행되기 전에 다른 스레드가 먼저 실행되어 조건을 변경함.
- wait 하는 data processing thread에서 lambda로 한번 더 확인하는 이유이다.
- 깨어났다고(notify) 해서 조건이 만족되었다고 가정하면 안 된다. 시스템 상의 에러이다.
typedef int data_chunk;
#include <mutex>
#include <queue>
#include <condition_variable>
std::mutex mut;
std::queue<data_chunk> data_queue;
std::condition_variable data_cond;
void data_preparation_thread() {
while (true) { // more_data_to_prepare()
data_chunk const data = 3; // prepare_data()
{
std::lock_guard<std::mutex> lk(mut);
data_queue.push(data);
}
data_cond.notify_one();
}
}
void data_processing_thread() {
while (true) {
std::unique_lock<std::mutex> lk(mut);
auto is_not_empty = [] { return !data_queue.empty(); };
data_cond.wait(lk, is_not_empty);
data_chunk data = data_queue.front();
data_queue.pop();
lk.unlock();
// process(data);
if (!data_queue.empty()) // is_last_chunk(data)
break;
}
}
- data_preparation에선 scope 안에서 lock_guard를 사용하고, data_processing에선 unique_lock을 사용한다.
waiting thread는 wait할 때 unlock 해야 하기 때문이다. 만약 waiting thread에서 unlock을 안 하고 wait 하면, prepare node에서 데이터를 추가할 수 없다. lock_gaurd는 unlock을 지원하지 않기에, wait 하는 부분엔 unique_lock을 사용했다.
prepare node에서도 lock_guard를 {}에 넣어서 조건 변수의 notify once하기 전에는 unlock상태의 mutex를 유지한다. mutex가 이미 잠금 해제된 후 호출되는 이유는, 대기 중인 스레드가 깨어났을 때 즉시 mutex를 잠금하고 작업을 수행할 수 있도록 하기 위함이다. 만약 mutex가 잠긴 상태에서 notify_one()를 호출하면, 대기 중인 스레드는 깨어난 직후 다시 mutex가 해제되길 기다려야 한다.
notify_one을 하면, 해당 mutex를 wait하고 있던 thread 중 하나가 일어나서 해당 wait condition을 체크한다.
ps.
(interrupt 방식 아닌가? polling) -> peripheral I/O method.
ref.
- C++ concurrency in action
- https://en.wikipedia.org/wiki/Spurious_wakeup
반응형
'Engineering > C++' 카테고리의 다른 글
| Chapter 4. Synchronizing Concurrent Operations. (2) - future (1) | 2025.01.22 |
|---|---|
| Chapter3. Sharing Data Between Threads (1) | 2025.01.20 |
| C++ STL 정리 (7) | 2024.12.19 |
| C++17 Class Template Argument Deduction (CTAD) (2) | 2024.12.19 |
| C++에서 auto vs decltype vs typeid (5) | 2024.06.10 |