본문 바로가기

Programming

이펙티브 모던 C++_7

항목 37. std::thread들을 모든 경로에서 합류 불가능하게 만들어라

= 이미 조인되거나, detach된 깔끔한 상태로 만들어라

= joinable 상태에서 소멸자가 호출되면 종료된다.

= 나중에 조인될 여지가 있는 스레드를 만들지 말자

 

 

ex) 조건이 false일 경우 조인되지 않고 t의 소멸자가 호출되어 프로그램이 종료된다.

종료되는 이유 :

암묵적 join을 할 경우, 올바른 상태가 되기까지 대기할 수는 없다. 종료하는 것이 맞다.

암묵적 detach를 할 경우, 소멸된 변수 localValue에 접근할 가능성이 있다. 종료하는 것이 맞다.

=> 때문에 joinable 상태의 소멸자가 호출되면 종료시키는 것이 옳다

bool doWork()
{
    int localValue = 6;
    std::thread t([localValue]
        {
            int result = 0;
            result += localValue;
        });

     if (true/*조건*/) {
         t.join();
         return true;
     }

    return false;
}

void doSomething()
{
    std::cout <<"실행" <<std::endl;
}
int main()
{
    doWork();
    doSomething();
}

 

=>해결책

RAII (Resource acquisition is initialization) 방식을 사용한다.

더보기

RAII이란?

스마트 포인터들 처럼 해당 유효 공간이 종료되면 같이 소멸되는 방식

RAII을 통해 마지막에 항상 joinable여부를 체크하여 join or detach를 확실히 하도록 한다.

RAII을 통해 마지막 명시적으로 함수의 join여부를 체크하도록 한다.

 

 


 

 

항목 38. 스레드 핸들 소멸자들의 다양한 행동 방식을 주의하라

1. 미래 객체의 소멸자는 그냥 미래 객체의 자료 멤버들을 파괴할 뿐이다.

2. std::async를 통해 시동된 비지연 과제에 대한 공유 상태를 참조하는 마지막 미래 객체의 소멸자는 그 과제가 완료될 때까지 차단된다.

 

 


 

항목 39. 단발성 사건 통신에는 void 미래 객체를 고려하라.

하나의 스레드가 끝난 후 시작되는 스레드가 있다면?

1. 다음 스레드가 일어날 수 있는지 검출한다.

2. 이를 반응 과제에게 알린다

 

ex) std::promise 사용

: 미래에 약속된 데이터를 받을 때 까지 기다린다.

std::promise<void> p;					 // 통신 채널에서 사용할 약속 객체

// 반응 과제
void react();

// 검출 과제
void detect()
{
  ThreadRAII tr(
    std::thread([]
                {
                  p.get_future().wait();  // 반응 준비, p에 해당하는 미래 객체를 기다린다.
                  react();
                }),
    ThreadRAII::DtorAction::join
  );
  ...
  p.set_value(); 						  // 사건 검출하고 반응 과제에게 통지 한다.
  ...
}

 

1. 간단한 사건 통신을 수행할 때, 조건 변수 기반 설계에는 여분의 뮤텍스가 필요하고 검출, 반응 과제 진행에 제약에 있으며 사건이 실제로 발생하는지 반응 과제가 다시 확인해야한다. (ex) noti했는데 아직 thread가 안 깨어나서 놓쳤을 경우)

2. 플래그 기반 설계를 사용하면 폴링이 일어나 불필요한 낭비가 생긴다.

3. 조건 변수, 플래그 조합할 수 있으나 복잡하다.

4. std::promise와 미래 객체를 사용하면 위의 문제를 피할 수 있지만 이런 접근 방식은 공유 상태에 힙 메모리를 사용하며 단발성 통신만 가능하다.

 

 


 

 

 

항목 40. 동시성에는 std::atomic을 사용하고 , volatile은 특별한 메모리에 사용하라.

volatile : 레지스터에 로드된 값을 사용하지 않고 매번 메모리에 접근하여 가져온 값을 사용한다.

std::atomic : 원자성을 보존하여 두개의 스레드가 동시에 접근해도 data race가 일어나지 않는다(어셈블리 단위에서 보았을 때 쓰기/읽기를 동시에 하지 않는다.)

 

최적화를 위한 재배치 

volatile (o)

std::atomic (x)

 

특별한 메모리

: volatile, 이 메모리에 대한 연산들에는 그 어떠한 최적화도 수행하지 말라.

: atomic에서는 최적화가 수행된다.

 

=그러므로 동시성에는 std::atomic을 사용하고 특별한 메모리에는 vilatile을 사용하는 것이 맞다.

 

 

'Programming' 카테고리의 다른 글

이펙티브 STL_1  (0) 2021.01.31
이펙티브 모던 C++_8  (0) 2020.12.20
이펙티브 모던 C++_6  (0) 2020.12.06
이펙티브 모던 C++_5  (0) 2020.11.29
이펙티브 모던 C++_4  (0) 2020.11.22