《출처. 시작하자! C++17 프로그래밍 (박헌재 지음)》
독립적으로 실행되는 스레드라 하더라도 경우에 따라 다른 스레드에 전달할 정보가 있기 마련이다. 전달하는 방법 중 우리가 공부할 방법은 '조건변수(Conditional Variable)' 라고 부르는 기능이다.
조건변수는 주로 게임에서 많이 사용하는 데 달리기 시합처럼 모든 선수가 출발선상에 대기한 상태에서 총소리와 함께 출발하도록 스레드 모두 대기 상태로 만들고 동시에 공동 경쟁을 수행한다.
조건변수는 단지 변수를 통해 신호를 주고 받는 기능만을 제공할 뿐 자체 잠금 기능이 없다. 따라서 다수의 스레드에 의해 실행되는 작업이 안전성을 보장하기 위해 별도 뮤텍스를 사용한다.
가장 중요한 함수는 wait(), notify_all() 함수이다.
조건변수가 신호를 받으면, 작업을 수행하기 전에 가장 먼저 wait()함수 내부에서 뮤텍스에 대한 잠금을 다시 설정하고 임계 구역으로 진입하여 작업을 수행한다.
※임계구역
1. void wait(std::unique_lock<std::mutex>& lock)
다른 스레드로부터 신호를 받을 때까지 대기하는 기능을 제공한다. 이때 임계 구역 내 존재하는 작업을 수행하기 위해 뮤텍스를 사용한다. 주의점은 wait()함수를 호출하기 전에 먼저 뮤텍스의 잠금을 설정해야 한다.
조건 변수의 신호를 받았다 하더라도 뮤텍스의 잠금이 설정되어 있으면 해제될 때까지 대기하게 된다.
2. void notify_all()
조건 변수의 신호를 기다리는 wait()함수에 신호를 전송한다.
신호를 받은 스레드는 즉시 깨어나서 실행되는 것이 아니라 신호를 발생한 스레드가 뮤텍스의 잠금을 해재시킬 때 비로서 신호를 전달받은 스레드는 작동하게 된다.
작업이 완료되어 프로그램 제어가 지역 범위를 벗어난다면 std::unique_lock 클래스의 특성에 따라 뮤텍스의 잠금이 자동으로 해제되는 동시에 객체가 소멸된다.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
static int ranking = 1;
void print(int id)
{
// 뮤텍스와 함께 unique_lock 객체를 사용하여 잠금을 설정
std::unique_lock<std::mutex> umtx(mtx);
// 조건변수 클래스가 제공하는 wait()함수를 호출하고 조건 변수의 신호를 기다림.
// wait()함수는 내부에서 앞에서 설정한 뮤텍스의 잠금을 해제시키고 조건 변수
// 통해 신호가 들어오기까지 대기하는 기능을 제공한다.
cv.wait(umtx);
std::cout << ranking++ << ". thread" << id << "\n";
}
int main()
{
std::thread threads[10];
for (int i = 0; i < 10; ++i)
{
//작업을 수행할 스레드를 생성하고 스레드를 실행시킨다.
threads[i] = std::thread(print, i);
}
std::cout << "10개의 스레드가 레이스 경쟁를 펼친다.\n";
//모든 스레드가 wait()함수를 호출할 때까지 잠시 대기한다.
std::this_thread::sleep_for(std::chrono::milliseconds(10));
{
std::unique_lock<std::mutex> umtx(mtx);
cv.notify_all();//모든 스레드에 신호를 보낸다.
/*뮤텍스 잠금을 설정되었다는 의미는 모든 일반 스레드에서 wait()함수를
호출하고 뮤텍스의 잠금을 해제시켰다는 의미가 된다. 따라서 뮤텍스 잠금을 설정
하고 임계구역으로 진입하여 모든 조건 변수에 신호를 보내 작업 개시를 알린다.*/
}
for (auto& th : threads) th.join();
return 0;
}
메인스레드가 보낸 신호는 모든 일반 스레드에 거의 동시에 신호를 보냈지만, 그 가운데 가장 먼저 신호를 받은 스레드가 가장 먼저 해제된 뮤텍스에 대해 잠금을 설정한다.
신호를 받은 다른 스레드는 어느 하나의 스레드에 의해 뮤텍스 잠금이 설정되어있기 때문에 다시 대기 상태에 들어가게 된다. 뮤텍스 잠금을 설정한 스레드가 작업을 마무리하고 뮤텍스의 잠금을 푼다면 그 때 나머지 스레드들이 순차적으로 뮤텍스의 잠금을 설정하고 작업을 마무리하게 된다.
조건변수 클래스가 제공하는 함수는?
● void wait(std:: unique_lock<std::mutex>& lock) : <mutex>
wait()함수는 다른 스레드로부터 신호를 받을 때까지 대기하는 기능을 제공한다.
● template<class Predicate>
void wait(std::unique_lock<std::mutex>& lock, Predicate pred) : <mutex>
위의 두 번째 인수로 제공된 pred는 bool 타입의 데이터를 반환하는 함수로 다음과 같은 기능을 수행한다.
while(!pred()) {
cv.wait(umtx);
}
함수가 거짓이면 반복 작업을 수행한다.
● template <class Rep, class Period>
cv_state wait_for(unique_lock<mutex>&lck, const chrono::duration<Rep, Period>& rel_time) : <mutex>
● template <class Rep, class Period, class Predicate>
bool wait_for (unique_lock<mutex>& lck, const chrono::duration<Repm Period>& rel_time, Predicate pred) : <mutex>
같은 기능이지만 대기하는 시간 간격이 설정되어 있어 시간 간격을 넘는다면 그에 따라 각각 다른 반환값을 반환한다.
● template<class Clock, class Duration>
std::cv_status wait_until(std::unique_lock<std::mutex>& lock, const std::chrono::time_point<Clockm Duration>&
timeout_time) : <mutex>
● template <class Clock, class Duration, class Predicate>
bool wait_until (std::unique_lock<std::mutex>& lock, const std::chrono::time_point<Clock, Duration>& timeout_time, Predicate pred) : <mutex>
주어진 시간 간격이 아닌 미래의 시간을 기준으로 한다.
● void notify_all() noexcept
● void notify_one() noexcept
notify_all함수는 조건 변수의 신호를 대기하는 모든 스레드에 거의 동시에 신호를 전송한다.
반면에 notify_one()함수는 조건 변수의 신호를 대기하는 스레드 가운데 하나의 스레드를 선택하여 신호를 전달한다.
※ noexcept ? c++함수의 키워드로 'noexcept 키워드를 붙이면 해당 함수가 예외를 throw 하지 않는다' 이게 무슨 말인지 설명하면 C++에서는 함수가 예외 처리를 하는 방법은 2가지가 있는데 함수가 예외를 던질 가능성이 있는지 아니면 절대 예외를 던지지 않는지 이렇게 두가지 방법이 있는데 후자 방법이 noexcept 키워드를 사용하는 것입니다.
만약 noexcept로 명시된 함수가 예외를 발생시킨다 해도 예외가 제대로 처리되지 않고 프로그램은 종료 됩니다.
'👨🏻💻 programming > ◽ c, c++' 카테고리의 다른 글
[C++] 제네릭 알고리즘 모음 (0) | 2022.05.16 |
---|---|
[C++] std::string_view 클래스 (0) | 2022.02.09 |
[C++] 연산자 오버로딩 프로토타입 & 한계 (0) | 2022.02.08 |
(전문가를 위한 C++/ 개정 4판) - I/O 입출력 완전 분석 (0) | 2022.01.31 |
[C++] 비동기 프로그래밍 (0) | 2021.03.14 |
안 하는 것 보다 낫겠지
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!