memory_order_release, memory_order_acquire 는 아래 글을 참고 하면 되겠다
(atomic 예시 와 Memory Model (정책) , memory_order_...) https://3dmpengines.tistory.com/2199
C++11은 두 개의 memory fence(memory barrier) 기능을 제공한다.
fence는 non-atomic operation 또는 memory_order_relaxed로 수행되는
atomic operation시 메모리 배리어 역할을 수행한다.
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에서의 producer
-
void TrySendMessage()
-
{
-
//...
-
-
g_payload.tick = clock();
-
g_payload.str = "TestMessage";
-
g_payload.param = param;
-
-
// 지금까지 쓴 내용들이 Acquire를 수행한 쓰레드에서 보여져야 한다.
-
atomic_thread_fence(memory_order_release);
-
-
g_guard.store(1, memory_order_relaxed);
-
}
-
-
// 쓰레드 2에서 대기중인 consumer
-
void TryReceiveMessage()
-
{
-
// ...
-
-
// Load 수행
-
int ready = g_guard.load(memory_order_relaxed);
-
if (ready != 0)
-
{
-
atomic_thread_fence(memory_order_acquire);
-
// 이후부터는 Release 이전에 쓰여진 메모리 값들이 모두 제대로 보여야 한다
-
-
result.tick = g_payload.tick;
-
result.str = g_payload.str;
-
result.param = g_payload.param;
-
}
-
}
위 코드의 흐름을 그림으로 표현하면 아래와 같다.
또 하나의 예를 들자면, 싱글턴에서 자주 사용되는 Double-Checked Locking 기법일 것이다.
-
using namespace std;
-
-
atomic<Singleton*> Singleton::m_instance;
-
mutex Singleton::m_mutex;
-
-
// Double-Checked Locking 기법에 relaxed order와 memory fence 활용
-
Singleton* Singleton::getInstance()
-
{
-
Singleton* tmp = m_instance.load(memory_order_relaxed);
-
atomic_thread_fence(memory_order_acquire);
-
if (tmp == nullptr)
-
{
-
lock_guard<mutex> lock(m_mutex);
-
-
tmp = m_instance.load(memory_order_relaxed);
-
if (tmp == nullptr)
-
{
-
tmp = new Singleton;
-
-
atomic_thread_fence(memory_order_release);
-
m_instance.store(tmp, memory_order_relaxed);
-
}
-
}
-
return tmp;
-
}
위 코드의 흐름은 아래 그림과 같다.
헌데, 아무리 sequential consistency가 위 방식보다 조금 덜 효율적이라 하지만,
실제 퍼포먼스 테스트를 해 본 결과 의미를 부여할 만한 성능 차이를 전혀 발견하지 못했다.
아래 예제는 C++ atomic default인 memory_order_seq_cst 버전이다. 깔끔!!!
(Intel TBB로 구현했다면, default가 release-acquire)
-
// C++11 디폴트 sequential consistency 버전
-
Singleton* Singleton::getInstance()
-
{
-
Singleton* inst = m_instance.load();
-
if (inst == nullptr)
-
{
-
lock_guard<std::mutex> lock(m_mutex);
-
-
inst = m_instance.load(/*memory_order_seq_cst*/);
-
if (inst == nullptr)
-
{
-
inst = new Singleton;
-
m_instance.store(inst/*, memory_order_seq_cst*/);
-
}
-
}
-
return inst;
-
}
2) atomic_signal_fence(memory_order order)
메모리 재배치를 금지한다.
4. 추가 링크
아래 링크들이 워낙 좋아 추가로 링크를 걸어둔다.
아티클 안에 링크된 페이지들도 읽어 볼 만한 내용이 많으므로, 시간날 때마다 탐독할 것.
(사실 위 내용들의 예제/그림들 중 아래 링크들에서 퍼온 게 많다)
반응형
'운영체제 & 병렬처리 > Multithread' 카테고리의 다른 글
Singleton multithreading programs (2) (0) | 2022.09.13 |
---|---|
Singleton multithreading programs (1) (0) | 2022.09.13 |
atomic 예시 와 Memory Model (정책) , memory_order_... (0) | 2022.09.12 |
원자적 연산과 동일한객체 동일한 수정 순서 (0) | 2022.09.12 |
코드 재배치 및 메모리 가시성문제 예제 (0) | 2022.09.12 |