스레드를 순서대로 동기화시키는 방법에 관해 다룬다.
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 자원도 낭비되고, 기다려야 하는 초를 계산하기 힘들다. 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(); } }
- Conditional Variable (C++11)
위의 문제점을 해결하기 위해 C++11부터 Conditional Variable 도입됐다. CV를 활용하면, Thread가 해당 상태가 될 때까지 Wait, 어떤 상태가 됐을 때 Notify 할 수 있다. 두 가지 구현방식이 있다.
#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
std::mutex mut;
std::queue 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](std::mutex) lk(mut);
data_queue.push(data); //
}
data_cond.notify_one();//
}
}
void data_processing_thread()
{
while(true)
{
std::unique_lock[std::mutex](std::mutex) lk(mut);//
auto is_empty = []{return !data_queue.empty();};
data_cond.wait(lk, is_empty);// true가 아닌 이유, lambda로 한번 더 확인하는 이유, spurious wake 때문! notify 됐다고 해서 바로 실행 해도 되는 것 아님!
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 (0) | 2025.01.22 |
---|---|
Chapter3. Sharing Data Between Threads (0) | 2025.01.20 |
C++ STL 정리 (1) | 2024.12.19 |
C++17 Class Template Argument Deduction (CTAD) (0) | 2024.12.19 |
C++에서 auto vs decltype vs typeid (0) | 2024.06.10 |