반응형

 

memory_order_release, memory_order_acquire 는  아래 글을 참고 하면 되겠다

(atomic 예시 와 Memory Model (정책) , memory_order_...) https://3dmpengines.tistory.com/2199

 

atomic 예시 와 Memory Model (정책) , memory_order_...

atomic flag = false; //true 가 나오면 원래 원자적으로 처리 되는거라 lock 할 필요 없음 bool isAtomic = flag.is_lock_free(); //flag.store(true); flag.store(true, memory_order::memory_order_seq_cst); /..

3dmpengines.tistory.com

 

 

 

 

C++11은 두 개의 memory fence(memory barrier) 기능을 제공한다.

 

fence는 non-atomic operation 또는 memory_order_relaxed로 수행되는
atomic operation 메모리 배리어 역할을 수행한다.

 

 

1) atomic_thread_fence(memory_order order)

메모리 가시성 강제메모리 재배치금지한다.
사실 fence는 atomic 클래스나 atomic instrinsic 함수들을 사용한다면, 굳이 사용할 필요가 없다고 생각한다.
 
굳이 이를 사용하려면, atomic 멤버 함수 호출시 memory_order_relaxed(메모리 배치에 관여하지 않음) 방식을 쓴 뒤,
인위적으로 fence를 호출해 주는 방식을 써야 하는데 굳이 쓸 일이 있을까 싶다.
 
앞서 소개했던 Write-Release / Read-Acquire 예제를 atomic_thread_fence 버전으로 바꿔보자.
 
  1. // 쓰레드 1에서의 producer
  2. void TrySendMessage()
  3. {
  4.     //...
  5.    
  6.     g_payload.tick = clock();
  7.     g_payload.str = "TestMessage";
  8.     g_payload.param = param;
  9.  
  10.     // 지금까지 쓴 내용들이 Acquire를 수행한 쓰레드에서 보여져야 한다.
  11.     atomic_thread_fence(memory_order_release);
  12.  
  13.     g_guard.store(1, memory_order_relaxed);
  14. }
  15.  
  16. // 쓰레드 2에서 대기중인 consumer
  17. void TryReceiveMessage()
  18. {
  19.     // ...
  20.  
  21.     // Load 수행
  22.     int ready = g_guard.load(memory_order_relaxed);
  23.     if (ready != 0)
  24.     {
  25.         atomic_thread_fence(memory_order_acquire);
  26.         // 이후부터는 Release 이전에 쓰여진 메모리 값들이 모두 제대로 보여야 한다
  27.  
  28.         result.tick = g_payload.tick;
  29.         result.str = g_payload.str;
  30.         result.param = g_payload.param;
  31.     }
  32. }
 
위 코드의 흐름을 그림으로 표현하면 아래와 같다.
 
 
또 하나의 예를 들자면, 싱글턴에서 자주 사용되는 Double-Checked Locking 기법일 것이다.
 
  1. using namespace std;
  2.  
  3. atomic<Singleton*> Singleton::m_instance;
  4. mutex Singleton::m_mutex;
  5.  
  6. // Double-Checked Locking 기법에 relaxed order와 memory fence 활용
  7. Singleton* Singleton::getInstance()
  8. {
  9.     Singleton* tmp = m_instance.load(memory_order_relaxed);
  10.     atomic_thread_fence(memory_order_acquire)
  11.     if (tmp == nullptr)
  12.     {
  13.         lock_guard<mutex> lock(m_mutex);
  14.  
  15.         tmp = m_instance.load(memory_order_relaxed);
  16.         if (tmp == nullptr)
  17.         {
  18.             tmp = new Singleton;
  19.  
  20.             atomic_thread_fence(memory_order_release);
  21.             m_instance.store(tmp, memory_order_relaxed);
  22.         }
  23.     }
  24.     return tmp;
  25. }
 
 
 
 
 
 
위 코드의 흐름은 아래 그림과 같다.
 
헌데, 아무리 sequential consistency가 위 방식보다 조금 덜 효율적이라 하지만, 
실제 퍼포먼스 테스트를 해 본 결과 의미를 부여할 만한 성능 차이를 전혀 발견하지 못했다.
 
아래 예제는 C++ atomic default인 memory_order_seq_cst 버전이다. 깔끔!!!
(Intel TBB로 구현했다면, default가 release-acquire)
 
  1. // C++11 디폴트 sequential consistency 버전
  2. Singleton* Singleton::getInstance()
  3. {
  4.     Singleton* inst = m_instance.load();
  5.     if (inst == nullptr)
  6.     {
  7.         lock_guard<std::mutex> lock(m_mutex);
  8.  
  9.         inst = m_instance.load(/*memory_order_seq_cst*/);
  10.         if (inst == nullptr)
  11.         {
  12.             inst = new Singleton;
  13.             m_instance.store(inst/*, memory_order_seq_cst*/);
  14.         }
  15.     }
  16.     return inst;
  17. }
 
 

2) atomic_signal_fence(memory_order order)

 
메모리 재배치를 금지한다.
 
4. 추가 링크
 
아래 링크들이 워낙 좋아 추가로 링크를 걸어둔다.
아티클 안에 링크된 페이지들도 읽어 볼 만한 내용이 많으므로, 시간날 때마다 탐독할 것.
(사실 위 내용들의 예제/그림들 중 아래 링크들에서 퍼온 게 많다)
 
반응형

+ Recent posts