-
항목16. const멤버 함수를 스레드에 안전하게 작성하라Effective Modern C++ 2022. 7. 29. 22:57
const 함수 내부에서는 멤버 변수들의 값을 바꾸는 것이 불가능하다. 하지만, 만약에 멤버 변수를 mutable 로 선언하였다면 const 함수에서도 이들 값을 바꿀 수 있다.
class Widget { public: double roots() const { ... } private: mutable int rootVals = 0; } Widget p; ... /*--- 스레드1 ---*/ /*--- 스레드2 ---*/ // roots() 는 const 멤버함수다. // mutable로 선언한 변수는 변경이 가능하다. 자료 경쟁 가능성있음! auto root1 = p.roots(); auto root2 = p.roots();같은 메모리를 동기화 없이 읽고 쓰면 스레드1과 스레드2는 자료 경쟁에 빠진다.
이런 경우 통상적인 동기화 수단인 뮤텍스를 사용한다.class Widget { public: double roots() const { std::lock_guard<std::mutex> g(m); ... } private: mutable std::mutex m; mutable int rootVals = 0; } Widget p;std::mutex 형식의 객체 m은 mutable 선언되었다. 그 이유는 m을 잠그고 푸는 멤버 함수들은 비const이지만 roots안에서는 m이 const객체로 간주되므로 이렇게 해야 한다.
std::mutex는 이동과 복사가 불가능하다. 그래서 m을 추가한 Widget도 이동과 복사 능력도 사라진다.(뒤에 나오는 std::atomic 도 마찬가지로 이동과 복사가 불가능)
멤버 함수의 호출 횟수를 세고 싶다면 뮤텍스를 도입하는 것 외에 std::atomic을 사용해 비용을 줄일 수 있다.class Point{ public: ... double distanceFromOrigin() const noexcept { ++callCount //원자적 증가 ... } private: mutable std::atomic<unsigned> callCount(0); }밑에는 std::atomic을 두개 이상 같이 사용한 상황이다. 이는 상당히 위험하다.
class Widget{ public: ... int magicValue() const { if(cacheValid) return cacheValue; else{ ... cacheValue = val1 + val2; cacheValid = true; return cachedValue; } } private: mutable std::atomic<bool> cacheValid(false); mutable std::atomic<int> cachedValue; }std::atomic을 두개 이상 같이 사용하면 스레드 두개 이상이 하나의 크리티칼 섹션에 들어가기 때문에 어떻게 작동할지 알 수 없게 된다.
동기화가 필요한 변수 하나 또는 메모리 장소 하나에 대해서는 std::atomic을 사용하는 것이 적합하지만, 둘 이상의 변수나 메모리 장소를 하나의 단위로서 조작해야 할 때에는 뮤텍스을 사용하는 것이 바람직하다.
'Effective Modern C++' 카테고리의 다른 글
항목18. 소유권 독점 자원 관리는 std__unique_ptr를 사용하라 (0) 2022.08.03 항목17. 특수 멤버 함수들의 자동 작성 조건을 숙지하라. (0) 2022.07.29 항목15. 가능하면 항상 constexpr을 사용하라 (0) 2022.07.28 항목14. 예외를 방출하지 않을 함수는 noexcept로 선언하라 (0) 2022.07.27 항목13. iterator보다 const_iterator를 선호하라 (0) 2022.07.27