#!if 문서명2 != null
, [[데이터 의존성]]
#!if 문서명3 != null
, [[뮤텍스]]
#!if 문서명4 != null
, [[파이프라인(CPU)]]
#!if 문서명5 != null
, [[]]
#!if 문서명6 != null
, [[]]
1. 개요
||<-12><tablewidth=80%><tablebordercolor=#20b580><tablebgcolor=#090f0a,#050b09><tablecolor=#a8a8a8,#c1c1c1><rowcolor=#d7d7d7,#a1a1a1>
<atomic>
||<bgcolor=#20b580> | |||||||||||
| |||||||||||
클래스 std::atomic 는 자명한 자료형에 대하여 원자적 사칙연산, 교환 (Swap, Exchange), 스레드 대기 및 깨우기 기능C++20 을 갖추고 있다. 참고로 자명한 자료형에는 포인터도 포함되며 자세한 내용은 항목 참조. | |||||||||||
<bgcolor=#20b580> | |||||||||||
| C++20 | ||||||||||
클래스 std::atomic_ref 는 자명하지 않은 자료형, 그리고 미리 std::atomic<T> 로 선언하지 않았던 변수에 대해서도 원자적 연산을 해주는 클래스다. 가령 클래스에는 int 데이터 멤버로 선언해놨다가 특정 함수에서만 원자적 연산을 실행시킬 수도 있다. | |||||||||||
<bgcolor=#20b580> |
<atomic>
C++11에서 추가된 모듈
<atomic>
은 원자적 연산과 메모리 장벽 기능을 지원하는 모듈이다.이 모듈은 다중 스레드 환경에서 발생하는 메모리 문제를 해결할 수 있다. 다중 스레드 환경에서는 기계어 명령어의 순서나 실행 방식에 따라 메모리 관련 문제가 생기기 쉽다. 보통 메모리에 접근하는 기계어 명령어 세트는 CPU, 컴파일러 명세에 따라 다르다. 해당 명령어들을 통합하는 하나의 인터페이스로 접근할 수 있도록 고안되었다.
2. 원자적 연산 (Atomic Operations)
#!if 리스트 == null
{{{#!html 이 용어}}}에 대한 내용은 {{{#!if 같은문서1 == null
[[뮤텍스]] 문서{{{#!if (문단1 == null) == (앵커1 == null)
를}}}{{{#!if (문단1 == null) != (앵커1 == null)
의 }}}}}}{{{#!if 문단1 != null & 앵커1 == null
[[뮤텍스#s-|]]번 문단을}}}{{{#!if 문단1 == null & 앵커1 != null
[[뮤텍스#참고: 원자적 연산(atomic operation)|참고: 원자적 연산(atomic operation)]] 부분을}}}{{{#!if 설명2 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서2 == null
[[]] 문서{{{#!if (문단2 == null) == (앵커2 == null)
를}}}{{{#!if (문단2 == null) != (앵커2 == null)
의 }}}}}}{{{#!if 문단2 != null & 앵커2 == null
[[#s-|]]번 문단을}}}{{{#!if 문단2 == null & 앵커2 != null
[[#|]] 부분을}}}}}}{{{#!if 설명3 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서3 == null
[[]] 문서{{{#!if (문단3 == null) == (앵커3 == null)
를}}}{{{#!if (문단3 == null) != (앵커3 == null)
의 }}}}}}{{{#!if 문단3 != null & 앵커3 == null
[[#s-|]]번 문단을}}}{{{#!if 문단3 == null & 앵커3 != null
[[#|]] 부분을}}}}}}{{{#!if 설명4 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서4 == null
[[]] 문서{{{#!if (문단4 == null) == (앵커4 == null)
를}}}{{{#!if (문단4 == null) != (앵커4 == null)
의 }}}}}}{{{#!if 문단4 != null & 앵커4 == null
[[#s-|]]번 문단을}}}{{{#!if 문단4 == null & 앵커4 != null
[[#|]] 부분을}}}}}}{{{#!if 설명5 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서5 == null
[[]] 문서{{{#!if (문단5 == null) == (앵커5 == null)
를}}}{{{#!if (문단5 == null) != (앵커5 == null)
의 }}}}}}{{{#!if 문단5 != null & 앵커5 == null
[[#s-|]]번 문단을}}}{{{#!if 문단5 == null & 앵커5 != null
[[#|]] 부분을}}}}}}{{{#!if 설명6 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서6 == null
[[]] 문서{{{#!if (문단6 == null) == (앵커6 == null)
를}}}{{{#!if (문단6 == null) != (앵커6 == null)
의 }}}}}}{{{#!if 문단6 != null & 앵커6 == null
[[#s-|]]번 문단을}}}{{{#!if 문단6 == null & 앵커6 != null
[[#|]] 부분을}}}}}}{{{#!if 설명7 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서7 == null
[[]] 문서{{{#!if (문단7 == null) == (앵커7 == null)
를}}}{{{#!if (문단7 == null) != (앵커7 == null)
의 }}}}}}{{{#!if 문단7 != null & 앵커7 == null
[[#s-|]]번 문단을}}}{{{#!if 문단7 == null & 앵커7 != null
[[#|]] 부분을}}}}}}{{{#!if 설명8 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서8 == null
[[]] 문서{{{#!if (문단8 == null) == (앵커8 == null)
를}}}{{{#!if (문단8 == null) != (앵커8 == null)
의 }}}}}}{{{#!if 문단8 != null & 앵커8 == null
[[#s-|]]번 문단을}}}{{{#!if 문단8 == null & 앵커8 != null
[[#|]] 부분을}}}}}}{{{#!if 설명9 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서9 == null
[[]] 문서{{{#!if (문단9 == null) == (앵커9 == null)
를}}}{{{#!if (문단9 == null) != (앵커9 == null)
의 }}}}}}{{{#!if 문단9 != null & 앵커9 == null
[[#s-|]]번 문단을}}}{{{#!if 문단9 == null & 앵커9 != null
[[#|]] 부분을}}}}}}{{{#!if 설명10 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서10 == null
[[]] 문서{{{#!if (문단10 == null) == (앵커10 == null)
를}}}{{{#!if (문단10 == null) != (앵커10 == null)
의 }}}}}}{{{#!if 문단10 != null & 앵커10 == null
[[#s-|]]번 문단을}}}{{{#!if 문단10 == null & 앵커10 != null
[[#|]] 부분을}}}}}}
#!if 리스트 != null
한시적 넘겨주기로 연결되는 다른 문서에 대한 내용은 아래 문서를
참고하십시오.#!if 리스트 != null
{{{#!if 문서명1 != null
* {{{#!if 설명1 != null
이 용어: }}}{{{#!if 같은문서1 == null
[[뮤텍스]] {{{#!if (문단1 == null) != (앵커1 == null)
문서의 }}}}}}{{{#!if 문단1 != null & 앵커1 == null
[[뮤텍스#s-|]]번 문단}}}{{{#!if 문단1 == null & 앵커1 != null
[[뮤텍스#참고: 원자적 연산(atomic operation)|참고: 원자적 연산(atomic operation)]] 부분}}}}}}{{{#!if 문서명2 != null
* {{{#!if 설명2 != null
: }}}{{{#!if 같은문서2 == null
[[]] {{{#!if (문단2 == null) != (앵커2 == null)
문서의 }}}}}}{{{#!if 문단2 != null & 앵커2 == null
[[#s-|]]번 문단}}}{{{#!if 문단2 == null & 앵커2 != null
[[#|]] 부분}}}}}}{{{#!if 문서명3 != null
* {{{#!if 설명3 != null
: }}}{{{#!if 같은문서3 == null
[[]] {{{#!if (문단3 == null) != (앵커3 == null)
문서의 }}}}}}{{{#!if 문단3 != null & 앵커3 == null
[[#s-|]]번 문단}}}{{{#!if 문단3 == null & 앵커3 != null
[[#|]] 부분}}}}}}{{{#!if 문서명4 != null
* {{{#!if 설명4 != null
: }}}{{{#!if 같은문서4 == null
[[]] {{{#!if (문단4 == null) != (앵커4 == null)
문서의 }}}}}}{{{#!if 문단4 != null & 앵커4 == null
[[#s-|]]번 문단}}}{{{#!if 문단4 == null & 앵커4 != null
[[#|]] 부분}}}}}}{{{#!if 문서명5 != null
* {{{#!if 설명5 != null
: }}}{{{#!if 같은문서5 == null
[[]] {{{#!if (문단5 == null) != (앵커5 == null)
문서의 }}}}}}{{{#!if 문단5 != null & 앵커5 == null
[[#s-|]]번 문단}}}{{{#!if 문단5 == null & 앵커5 != null
[[#|]] 부분}}}}}}{{{#!if 문서명6 != null
* {{{#!if 설명6 != null
: }}}{{{#!if 같은문서6 == null
[[]] {{{#!if (문단6 == null) != (앵커6 == null)
문서의 }}}}}}{{{#!if 문단6 != null & 앵커6 == null
[[#s-|]]번 문단}}}{{{#!if 문단6 == null & 앵커6 != null
[[#|]] 부분}}}}}}{{{#!if 문서명7 != null
* {{{#!if 설명7 != null
: }}}{{{#!if 같은문서7 == null
[[]] {{{#!if (문단7 == null) != (앵커7 == null)
문서의 }}}}}}{{{#!if 문단7 != null & 앵커7 == null
[[#s-|]]번 문단}}}{{{#!if 문단7 == null & 앵커7 != null
[[#|]] 부분}}}}}}{{{#!if 문서명8 != null
* {{{#!if 설명8 != null
: }}}{{{#!if 같은문서8 == null
[[]] {{{#!if (문단8 == null) != (앵커8 == null)
문서의 }}}}}}{{{#!if 문단8 != null & 앵커8 == null
[[#s-|]]번 문단}}}{{{#!if 문단8 == null & 앵커8 != null
[[#|]] 부분}}}}}}{{{#!if 문서명9 != null
* {{{#!if 설명9 != null
: }}}{{{#!if 같은문서9 == null
[[]] {{{#!if (문단9 == null) != (앵커9 == null)
문서의 }}}}}}{{{#!if 문단9 != null & 앵커9 == null
[[#s-|]]번 문단}}}{{{#!if 문단9 == null & 앵커9 != null
[[#|]] 부분}}}}}}{{{#!if 문서명10 != null
* {{{#!if 설명10 != null
: }}}{{{#!if 같은문서10 == null
[[]] {{{#!if (문단10 == null) != (앵커10 == null)
문서의 }}}}}}{{{#!if 문단10 != null & 앵커10 == null
[[#s-|]]번 문단}}}{{{#!if 문단10 == null & 앵커10 != null
[[#|]] 부분}}}}}}
원자적 연산 (Atomic Operations)
<thread>
문서의 뮤텍스 예제에서 원자적으로 실행된다는 언급을 했었다. 원자적으로 실행된다는 건 무슨 뜻일까? 원자는 더 이상 하위 입자로 쪼개지지 않는 입자를 말한다. 컴퓨터 공학에서 원자적 연산이란 모든 명령의 순서가 일관적으로 유지되며 중간 상태를 가지지 않고, 하위 기계어 명령어로 분해되지 않는 명령어를 말한다. 즉 원자적 명령어는 여러개의 기계어로 구현되지 않는다. 메모리 읽기/연산/쓰기가 전부 기계어 한줄에 나타난다. 그래서 앞서 봤던 경쟁 상태에서 발생했던 읽기/쓰기 명령어의 중첩이 일어나지 않는다.서로 다른 필드를 참조할 때 데이터의 접근 순서가 바뀔 수 있다는 사실도 알아본 바 있다. 이는 컴파일러와 CPU 둘다의 복합적인 상호작용의 결과다. C++ 코드에서는
volatile
을 붙이는 것 말고는 할 수 있는게 없고, 운영체제 스케쥴링은 성능에 지대한 영향을 미치긴 하나 이 사례와는 큰 연관이 없다. 명령어 실행 순서의 변경 혹은 하드웨어의 메모리 버스 상의 문제로 인해 메모리 접근 명령이 늦게 실행되는 경우는 있어도 누락되지는 일은 절대 일어나지 않기 때문이다. 원자적 연산은 컴파일러의 도움을 받아 CPU의 동작을 통제하는 방식으로 수행한다.3. 메모리 일관성 (Memory Coherence)
메모리 정렬 (Memory Ordering)#!if 리스트 == null
{{{#!html 이론적 배경}}}에 대한 내용은 {{{#!if 같은문서1 == null
[[데이터 의존성]] 문서{{{#!if (문단1 == null) == (앵커1 == null)
를}}}{{{#!if (문단1 == null) != (앵커1 == null)
의 }}}}}}{{{#!if 문단1 != null & 앵커1 == null
[[데이터 의존성#s-|]]번 문단을}}}{{{#!if 문단1 == null & 앵커1 != null
[[데이터 의존성#|]] 부분을}}}{{{#!if 설명2 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서2 == null
[[]] 문서{{{#!if (문단2 == null) == (앵커2 == null)
를}}}{{{#!if (문단2 == null) != (앵커2 == null)
의 }}}}}}{{{#!if 문단2 != null & 앵커2 == null
[[#s-|]]번 문단을}}}{{{#!if 문단2 == null & 앵커2 != null
[[#|]] 부분을}}}}}}{{{#!if 설명3 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서3 == null
[[]] 문서{{{#!if (문단3 == null) == (앵커3 == null)
를}}}{{{#!if (문단3 == null) != (앵커3 == null)
의 }}}}}}{{{#!if 문단3 != null & 앵커3 == null
[[#s-|]]번 문단을}}}{{{#!if 문단3 == null & 앵커3 != null
[[#|]] 부분을}}}}}}{{{#!if 설명4 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서4 == null
[[]] 문서{{{#!if (문단4 == null) == (앵커4 == null)
를}}}{{{#!if (문단4 == null) != (앵커4 == null)
의 }}}}}}{{{#!if 문단4 != null & 앵커4 == null
[[#s-|]]번 문단을}}}{{{#!if 문단4 == null & 앵커4 != null
[[#|]] 부분을}}}}}}{{{#!if 설명5 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서5 == null
[[]] 문서{{{#!if (문단5 == null) == (앵커5 == null)
를}}}{{{#!if (문단5 == null) != (앵커5 == null)
의 }}}}}}{{{#!if 문단5 != null & 앵커5 == null
[[#s-|]]번 문단을}}}{{{#!if 문단5 == null & 앵커5 != null
[[#|]] 부분을}}}}}}{{{#!if 설명6 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서6 == null
[[]] 문서{{{#!if (문단6 == null) == (앵커6 == null)
를}}}{{{#!if (문단6 == null) != (앵커6 == null)
의 }}}}}}{{{#!if 문단6 != null & 앵커6 == null
[[#s-|]]번 문단을}}}{{{#!if 문단6 == null & 앵커6 != null
[[#|]] 부분을}}}}}}{{{#!if 설명7 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서7 == null
[[]] 문서{{{#!if (문단7 == null) == (앵커7 == null)
를}}}{{{#!if (문단7 == null) != (앵커7 == null)
의 }}}}}}{{{#!if 문단7 != null & 앵커7 == null
[[#s-|]]번 문단을}}}{{{#!if 문단7 == null & 앵커7 != null
[[#|]] 부분을}}}}}}{{{#!if 설명8 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서8 == null
[[]] 문서{{{#!if (문단8 == null) == (앵커8 == null)
를}}}{{{#!if (문단8 == null) != (앵커8 == null)
의 }}}}}}{{{#!if 문단8 != null & 앵커8 == null
[[#s-|]]번 문단을}}}{{{#!if 문단8 == null & 앵커8 != null
[[#|]] 부분을}}}}}}{{{#!if 설명9 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서9 == null
[[]] 문서{{{#!if (문단9 == null) == (앵커9 == null)
를}}}{{{#!if (문단9 == null) != (앵커9 == null)
의 }}}}}}{{{#!if 문단9 != null & 앵커9 == null
[[#s-|]]번 문단을}}}{{{#!if 문단9 == null & 앵커9 != null
[[#|]] 부분을}}}}}}{{{#!if 설명10 != null
, {{{#!html }}}에 대한 내용은 {{{#!if 같은문서10 == null
[[]] 문서{{{#!if (문단10 == null) == (앵커10 == null)
를}}}{{{#!if (문단10 == null) != (앵커10 == null)
의 }}}}}}{{{#!if 문단10 != null & 앵커10 == null
[[#s-|]]번 문단을}}}{{{#!if 문단10 == null & 앵커10 != null
[[#|]] 부분을}}}}}}
#!if 리스트 != null
한시적 넘겨주기로 연결되는 다른 문서에 대한 내용은 아래 문서를
참고하십시오.#!if 리스트 != null
{{{#!if 문서명1 != null
* {{{#!if 설명1 != null
이론적 배경: }}}{{{#!if 같은문서1 == null
[[데이터 의존성]] {{{#!if (문단1 == null) != (앵커1 == null)
문서의 }}}}}}{{{#!if 문단1 != null & 앵커1 == null
[[데이터 의존성#s-|]]번 문단}}}{{{#!if 문단1 == null & 앵커1 != null
[[데이터 의존성#|]] 부분}}}}}}{{{#!if 문서명2 != null
* {{{#!if 설명2 != null
: }}}{{{#!if 같은문서2 == null
[[]] {{{#!if (문단2 == null) != (앵커2 == null)
문서의 }}}}}}{{{#!if 문단2 != null & 앵커2 == null
[[#s-|]]번 문단}}}{{{#!if 문단2 == null & 앵커2 != null
[[#|]] 부분}}}}}}{{{#!if 문서명3 != null
* {{{#!if 설명3 != null
: }}}{{{#!if 같은문서3 == null
[[]] {{{#!if (문단3 == null) != (앵커3 == null)
문서의 }}}}}}{{{#!if 문단3 != null & 앵커3 == null
[[#s-|]]번 문단}}}{{{#!if 문단3 == null & 앵커3 != null
[[#|]] 부분}}}}}}{{{#!if 문서명4 != null
* {{{#!if 설명4 != null
: }}}{{{#!if 같은문서4 == null
[[]] {{{#!if (문단4 == null) != (앵커4 == null)
문서의 }}}}}}{{{#!if 문단4 != null & 앵커4 == null
[[#s-|]]번 문단}}}{{{#!if 문단4 == null & 앵커4 != null
[[#|]] 부분}}}}}}{{{#!if 문서명5 != null
* {{{#!if 설명5 != null
: }}}{{{#!if 같은문서5 == null
[[]] {{{#!if (문단5 == null) != (앵커5 == null)
문서의 }}}}}}{{{#!if 문단5 != null & 앵커5 == null
[[#s-|]]번 문단}}}{{{#!if 문단5 == null & 앵커5 != null
[[#|]] 부분}}}}}}{{{#!if 문서명6 != null
* {{{#!if 설명6 != null
: }}}{{{#!if 같은문서6 == null
[[]] {{{#!if (문단6 == null) != (앵커6 == null)
문서의 }}}}}}{{{#!if 문단6 != null & 앵커6 == null
[[#s-|]]번 문단}}}{{{#!if 문단6 == null & 앵커6 != null
[[#|]] 부분}}}}}}{{{#!if 문서명7 != null
* {{{#!if 설명7 != null
: }}}{{{#!if 같은문서7 == null
[[]] {{{#!if (문단7 == null) != (앵커7 == null)
문서의 }}}}}}{{{#!if 문단7 != null & 앵커7 == null
[[#s-|]]번 문단}}}{{{#!if 문단7 == null & 앵커7 != null
[[#|]] 부분}}}}}}{{{#!if 문서명8 != null
* {{{#!if 설명8 != null
: }}}{{{#!if 같은문서8 == null
[[]] {{{#!if (문단8 == null) != (앵커8 == null)
문서의 }}}}}}{{{#!if 문단8 != null & 앵커8 == null
[[#s-|]]번 문단}}}{{{#!if 문단8 == null & 앵커8 != null
[[#|]] 부분}}}}}}{{{#!if 문서명9 != null
* {{{#!if 설명9 != null
: }}}{{{#!if 같은문서9 == null
[[]] {{{#!if (문단9 == null) != (앵커9 == null)
문서의 }}}}}}{{{#!if 문단9 != null & 앵커9 == null
[[#s-|]]번 문단}}}{{{#!if 문단9 == null & 앵커9 != null
[[#|]] 부분}}}}}}{{{#!if 문서명10 != null
* {{{#!if 설명10 != null
: }}}{{{#!if 같은문서10 == null
[[]] {{{#!if (문단10 == null) != (앵커10 == null)
문서의 }}}}}}{{{#!if 문단10 != null & 앵커10 == null
[[#s-|]]번 문단}}}{{{#!if 문단10 == null & 앵커10 != null
[[#|]] 부분}}}}}}
#!syntax cpp
volatile int a, b, c;
void Worker1()
{
a = 1;
b = 2;
// volatile에 의해 최적화되지 않음.
c = a;
}
void Worker2()
{
a = 2;
b = 1;
// volatile에 의해 최적화되지 않음.
c = b;
}
int main()
{
std::thread worker1(Worker1);
std::thread worker2(Worker2);
std::println("작업자 기다리는 중...");
worker1.join();
worker2.join();
std::println("작업자 종료됨.");
std::println("a: {}, b: {}, c: {}", a, b, c);
}
상기 코드에서 `a`
와 `b`
의 수정 순서가 일정하지 않음은 이미 앞서 살펴본 바 있다. 사례를 정리해보자.우리가 따져볼 연산 순서는 아래 네가지가 있다.
RWR (Read After Write)
: 읽기 후 읽기RAW (Read After Write)
: 읽기 후 쓰기WAR (Write After Read)
: 쓰기 후 읽기WAW (Write After Write)
: 쓰기 후 쓰기
#!syntax cpp
volatile int currWorkerID = 0;
volatile bool currWorkerFlags[2] = { false, false };
void Lock(int id)
{
const int other = 1 - id;
currWorkerFlags[id] = true;
currWorkerID = id;
while (currWorkerFlags[other] && currWorkerID == id);
}
void Unlock(int id)
{
currWorkerFlags[id] = false;
}
상기 코드는 피터슨 알고리즘이라는 2개의 스레드 사이에서 뮤텍스를 비롯한 운영체제 호출 없이 동기화를 수행하는 알고리즘이다. 이 알고리즘은 멈춤 & 대기 (Blocking) 동기화를 사용한다. 뮤텍스를 쓰진 않으나 사실상 C++ 코드로 동일한 동작을 구현한 것과 같다. 이론상 완벽하긴 하나 실제 실행 결과는 다르다.#!syntax cpp
volatile int sum = 0;
void Worker(int id);
int main()
{
}
AMD64 아키텍쳐에서는 거의 모든[1] 메모리 쓰기 작업이 원자적 연산이며 하나의 루틴(함수) 안에서는 하나의 메모리에 대하여 읽기/쓰기의 순서가 지켜진다. 다중 스레드 환경에서도 각각의 루틴에선 메모리 접근 순서가 지켜진다. 그러나 다른 스레드 사이에는 메모리 접근에 대한 사실을 관측(Visibillity, 가시성)할 수 없다. 설령 두 스레드가 동일한 메모리 블록을 사용하더라도 메모리 접근 순서가 지켜지지 않는다.[2] 심지어 메모리에 쓰기 작업을 실제로 실행했어도, 다른 스레드에서 수정한 메모리를 바로 관측할 수가 없다. 캐시 상태의 갱신에도 시간이 소요되기 때문이다.앞서 C++ 스레드 문서에서는 뮤텍스를 사용해서 스레드 간의 동기화를 수행했다. 그러나 뮤텍스는 성능 하락에 큰 영향을 미치며 특성상 세부적인 최적화가 쉽지 않다는 단점이 있었다.
추후 살펴볼 것이지만 AMD64 아키텍쳐에서는 읽기 연산에선 사실 락이 필요없음을 확인할 수 있다. 하지만 메모리 관측 여부 때문에 원자적 연산이 필요하다.
원자적 연산을 쓰면 상호 배제 전략을 완전히 대체할 수 있다. 문맥 전환을 줄이고 성능에 기여할 수 있다.[3]
한편 ARM 아키텍쳐에서는 거의 모든 메모리 접근이 원자적 연산이 아니며 순서도 지켜지지 않는다. 메모리 접근 사이에 의존성이 아예 없다. 그래서 AMD64보다 최적화에 유리한 면이 있다. 컴파일러의 도움이 아니면 단순한 함수 안에서 변수 대입 조차 순서가 바뀔 수 있다. 그래서 읽기 연산에서도 조치가 필요하다. 보통 LLSC라는 연산을 사용한다. AMD64 플랫폼에서 많이 쓰는 CAS 연산보다 훨씬 강력하며 메모리 위치를 기억하고, 읽기와 쓰기에 필요한 레지스터의 수가 적다.
4. 메모리 장벽 (Memory Fence)
메모리 장벽 (Memory Fence)메모리 장벽은 문자 그대로 메모리 상에 세워진 장벽이다. CPU가 메모리 접근 명령어를 이리저리 재배치하는 것을 방지한다.
C++ 스레드 문서에서 본 C++ 뮤텍스 역시 메모리 장벽 기능을 사용한다. 그래서 뮤텍스를 잠그고 푸는 동작은 원자적 연산이며, 하나의 뮤텍스를 잠그고 푸는 동작은 실행 순서가 고정된다. 뮤텍스로 잠그거나 해제한 구획의 명령어는 외부로 재배치되지 않는다. 설령 다른 두 스레드가 각각 뮤텍스를 잠그고 풀더라도 (교착 상태가 없다면) 아무 문제가 없이 순서가 고정된다.
#!syntax cpp
struct Container
{
std::mutex mtx1;
std::mutex mtx2;
void Send1()
{
mtx1.lock();
mtx1.unlock();
// 이 사이에 다른 스레드의 명령어가 끼어들 수 있음.
mtx2.lock();
mtx2.unlock();
}
void Send2()
{
mtx1.lock();
// 이 사이에 다른 스레드의 명령어가 끼어들 수 있음.
mtx2.lock();
mtx2.unlock();
// 이 사이에 다른 스레드의 명령어가 끼어들 수 있음.
mtx1.unlock();
}
};
그런데 다른 뮤텍스가 끼어들면 이상한 일이 일어난다. 곧 서로 다른 뮤텍스를 잠그고 풀 때 그 사이에 다른 명령어가 배치될 수 있다. 여기서 알 수 있는 사실은 다수의 원자적 연산/메모리 장벽을 결합한 연산이 원자적 연산이 아닐 수 있다는 것이다! 또한 원자적 연산과 메모리 장벽은 별개의 개념이며 따로 적용할 수도 있음을 알고 있어야 한다. 그래서 우리는 동시성 프로그래밍을 통해 어떻게 복합적인 원자적 연산을 구현할 수 있는지 알아볼 것이다.우리는 메모리 장벽을 써서 메모리 접근 순서가 섞이는 것을 막고, 원자적 연산으로 메모리 연산이 중첩되는 것을 막아야 한다.
4.1. std::atomic_thread_fence
<atomic>
모듈에서 제공하는 전역 함수 std::atomic_thread_fence(std::memory_order)
를 통해 수동으로 메모리 장벽을 세울 수 있다.유의할 점은
std::atomic_thread_fence
은 메모리 접근 순서를 보장하는 기능을 수행한다. 즉 컴파일러에 의한 메모리의 최적화는 막지 못한다.#!syntax cpp
volatile bool ready = false;
volatile int value;
void Producer()
{
while (!ready);
// `value` 가 반드시 `ready` 이후에 읽히는 순서를 보장함.
std::atomic_thread_fence(std::memory_order_seq_cst);
//std::atomic_thread_fence(std::emory_order_acquire); 로도 가능함.
std::println("`value`는 {}.", value);
}
void Consumer()
{
std::println("정수를 입력해주세요.");
std::cin >> value;
// `ready`가 반드시 `value` 이후에 수정되는 순서를 보장함.
std::atomic_thread_fence(std::memory_order_release);
//std::atomic_thread_fence(std::memory_order_acquire); 로도 가능함.
ready = true;
}
상기 코드는 메모리 장벽의 용례를 보여주고 있다. 여기서 전역 변수 `ready`
, `value`
는 반드시 volatile
로 선언해야 한다. 안 그러면 최적화로 구문이 소멸할 수도 있다. 가령 ready = true;
같은 문장은 입력을 받기도 전에 먼저 실행될 수도 있다.열거형
`memory_order`
에 대해서는 다음 문단에서 설명한다.5. 메모리 모델
#!syntax cpp
namespace std
{
enum class memory_order
{
relaxed,
consume,
acquire,
release,
acq_rel,
seq_cst
};
inline constexpr memory_order memory_order_relaxed = memory_order::relaxed;
inline constexpr memory_order memory_order_consume = memory_order::consume;
inline constexpr memory_order memory_order_acquire = memory_order::acquire;
inline constexpr memory_order memory_order_release = memory_order::release;
inline constexpr memory_order memory_order_acq_rel = memory_order::acq_rel;
inline constexpr memory_order memory_order_seq_cst = memory_order::seq_cst;
}
5.1. relaxed
오직 원자적 연산만 수행하며 접근 순서는 보장하지 않음.5.2. release & acquire
5.2.1. acquire
원자적 연산과 함께 읽기 순서를 보장함.한 스레드에서 쓰기 읽기 작업이 순서가 유지됨.
한 스레드에서 읽기 작업 뒤에 다른 읽기/쓰기 작업이 배치될 수 없음.
5.2.2. release
원자적 연산과 함께 쓰기 순서를 보장함.한 스레드에서 쓰기 작업이 순서가 유지됨.
한 스레드에서 쓰기 작업 뒤에 다른 읽기/쓰기 작업이 배치될 수 없음.
한 스레드에서 쓴 메모리는 반드시 다른 스레드에서 관측할 수 있음.
5.2.3. acq_rel
5.3. seq_cst
원자적 연산과 함께 접근 순서를 보장함.모든 스레드에서 쓰기 작업과 읽기 작업이 서로 중첩되지 않음.
모든 스레드에서 쓰기 작업과 읽기 작업이 각각 순서가 유지됨.
읽기 작업에 acquire를 사용함.
쓰기 작업에 release를 사용함.
수정(RMW) 작업에 acq_rel를 사용함.
6. std::atomic
||||<tablewidth=80%><tablebordercolor=#20b580><tablebgcolor=#090f0a,#050b09><tablecolor=#a8a8a8,#c1c1c1><rowcolor=#d7d7d7,#a1a1a1>
||
#!if head_comment!=null
{{{#57A64A,#5EBD46 }}}
#!if head_lnb!=null
##======================================= include and import
[br]
#!if import!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{import }}}}}}'''{{{#C8865E {{{<>}}}}}}{{{;}}}
#!if include!=null
##======================================= the keyword at head (can be `template`)
{{{#include <>}}}
#!if (head_keyword_available = head_keyword != null)
##======================================= template parameters begin
'''{{{#DodgerBlue,#CornFlowerBlue {{{class}}}}}}'''
#!if template_available = (template_p0 != null || template_v0 != null || template_p1 != null || template_v1 != null || template_p2 != null || template_v2 != null || template_p3 != null || template_v3 != null)
#!if template_available || template_last_label != null
##======================================= template parameter 0
##======================================= template parameter 0 concept
{{{<}}}{{{#!if template_concept0_available = (template_cpt0 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept0_params_available = (template_concept0_p0 != null || template_concept0_p1 != null || template_concept0_p2 != null || template_concept0_p3 != null)
{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{<}}}}}}{{{#!if template_concept0_params_available
##======================================= template parameter 0 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p0 != null || template_concept0_v0 != null) && (template_concept0_p1 != null || template_concept0_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p1 != null || template_concept0_v1 != null) && (template_concept0_p2 != null || template_concept0_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept0_p2 != null || template_concept0_v2 != null) && (template_concept0_p3 != null || template_concept0_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept0_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept0_params_available || template_concept0_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept0_available
{{{ }}}}}}{{{#!if template_p0_available = (template_p0 != null)
{{{#!if !template_concept0_available
##======================================= template parameter 0 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v0_available = (template_v0 != null)
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}
##======================================= template parameter 0 finished
}}}{{{#!if (template_p0_available || template_v0_available) && (template_p1 != null || template_v1 != null)
##======================================= template parameter 1
{{{, }}}}}}{{{#!if template_concept1_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept1_params_available = (template_concept1_p0 != null || template_concept1_p1 != null || template_concept1_p2 != null || template_concept1_p3 != null)
{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{<}}}}}}{{{#!if template_concept1_params_available
##======================================= template parameter 1 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p0 != null || template_concept1_v0 != null) && (template_concept1_p1 != null || template_concept1_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p1 != null || template_concept1_v1 != null) && (template_concept1_p2 != null || template_concept1_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept1_p2 != null || template_concept1_v2 != null) && (template_concept1_p3 != null || template_concept1_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept1_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept1_params_available || template_concept1_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept1_available
{{{ }}}}}}{{{#!if template_p1_available = (template_p1 != null)
{{{#!if !template_concept1_available
##======================================= template parameter 1 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}}}}{{{#!if template_v1_available = (template_v1 != null)
##======================================= template parameter 1 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if (template_p1_available || template_v1_available) && (template_p2 != null || template_v2 != null)
##======================================= template parameter 2
{{{, }}}}}}{{{#!if template_concept2_available = (template_cpt1 != null)
'''{{{#4ec9b0,#6fdbba {{{ }}}}}}'''}}}{{{#!if template_concept2_params_available = (template_concept2_p0 != null || template_concept2_p1 != null || template_concept2_p2 != null || template_concept2_p3 != null)
{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{<}}}}}}{{{#!if template_concept2_params_available
##======================================= template parameter 2 concept's parameters (type or value)
{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p0 != null || template_concept2_v0 != null) && (template_concept2_p1 != null || template_concept2_v1 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p1 != null || template_concept2_v1 != null) && (template_concept2_p2 != null || template_concept2_v2 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if (template_concept2_p2 != null || template_concept2_v2 != null) && (template_concept2_p3 != null || template_concept2_v3 != null)
{{{, }}}}}}{{{#4ec9b0,#6fdbba {{{}}}}}}'''{{{#ffffff {{{}}}}}}'''{{{#!if template_concept2_last_label != null
{{{,...}}}}}}}}}{{{#!if template_concept2_params_available || template_concept2_last_label != null
{{{>}}}}}}}}}{{{#!if template_concept2_available
{{{ }}}}}}{{{#!if template_p2_available = (template_p2 != null)
{{{#!if !template_concept2_available
##======================================= template parameter 2 contents
'''{{{#569cd6,#CornFlowerBlue {{{typename}}}}}}'''}}}{{{}}}{{{#4ec9b0,#6fdbba {{{ }}}}}}
}}}{{{#!if template_v2_available = (template_v2 != null)
##======================================= template parameter 2 finished
{{{#4ec9b0,#6fdbba {{{}}}}}}{{{}}}{{{#ffffff '''{{{ }}}'''}}}}}}{{{#!if template_available && template_last_label != null
##======================================= template finished
{{{, }}}}}}{{{#!if template_last_label != null
{{{...}}}}}}{{{#!if template_available || template_last_label != null
{{{>}}}}}}{{{#!if do_template_linebreak = (template_lnb != null)
##======================================= template linebreak
[br]}}}{{{#!if do_template_linebreak ;nbsp
}}}
#!if head_keyword_available!=null
{{{ }}}
#!if fn_attribute!=null
##======================================= contents
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr1]]}}}}}}]]
#!if fn_attribute_lnk!=null
[[C++/문법/특성#attr1|{{{#a8a8a8 {{{[[attr1]]}}}}}}]]
#!if fn_attribute_lnb!=null
[br]
#!if fn_attribute_lnb!=null ;nbsp
#!if kw1!=null
'''{{{#569cd6,#CornFlowerBlue {{{contexpr}}}}}}'''{{{#!if kw1_post!=null
{{{_kwp1}}}}}}{{{#!if kw1_post==null
{{{ }}}}}}
#!if kw2!=null
'''{{{#569cd6,#CornFlowerBlue {{{long long}}}}}}'''{{{#!if kw2post!=null
{{{&&}}}}}}{{{#!if kw2_post==null
{{{ }}}}}}
#!if cls_attribute!=null
[[C++/문법/특성|{{{#a8a8a8 {{{[[attr2]]}}}}}}]]{{{ }}}
#!if cls_attribute_lnk!=null
[[C++/문법/특성#attr2|{{{#a8a8a8 {{{[[attr2]] }}}}}}]]
#!if ns1!=null
##======================================= namespaces
'''{{{#58fafe {{{std}}}}}}'''
#!if ns2!=null
{{{::}}}'''{{{#58fafe {{{chrono}}}}}}'''
#!if ns1!=null
{{{::}}}
#!if pre1_t!=null
##======================================= types at the front
{{{#4ec9b0,#6fdbba {{{atomic }}}}}}
#!if pre2_t!=null
{{{::}}}{{{#4ec9b0,#6fdbba {{{duration }}}}}}
#!if pre_e!=null
{{{::}}}{{{#f0f068 {{{enum }}}}}}
#!if pre_post!=null
{{{_pre}}}
#!if pre_lnb!=null
[br]
#!if do_pre_linebreak = (pre_lnb != null)
##======================================= pre-body things finished
#!if (!do_pre_linebreak && !do_template_linebreak && head_keyword_available) && (body_v != null || body_f != null || body_mv != null || body_mf != null || body_post != null)
{{{ }}}
#!if body_v!=null
##======================================= identifiers of variable and function
{{{#a8a8a8,#a1a1a1 {{{val}}}}}}
#!if body_mv!=null
{{{#ffffff {{{mem_val}}}}}}
#!if body_f!=null
{{{#f87a7a {{{fun}}}}}}
#!if body_mf!=null
{{{#f0a962 {{{mem_fn}}}}}}
#!if body_post!=null
##======================================= argument 1
{{{}}}
#!if body_tmpopen!=null
{{{<}}}
#!if body_bopen!=null
{{{(}}}
#!if arg1_concept!=null
'''{{{#4ec9b0,#6fdbba {{{concept1}}}}}}'''{{{#!if arg1_concept_params!=null
{{{<}}}{{{#!if arg1_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg1_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg1_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg1_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg1_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg1_t!=null
{{{#4ec9b0,#6fdbba {{{type1}}}}}}
#!if arg1_t_post!=null
{{{&}}}
#!if arg1_param != null && (arg1_kw != null || arg1_t_kw != null || arg1_t != null)
{{{ }}}
#!if arg1_param!=null
{{{#bcdce6 {{{param1}}}}}}
#!if arg2_concept != null || arg2_kw != null || arg2_t_kw != null || arg2_t != null || arg2_param != null
{{{, }}}
#!if arg2_concept!=null
##======================================= argument 2
'''{{{#4ec9b0,#6fdbba {{{concept2}}}}}}'''{{{#!if arg2_concept_params!=null
{{{<}}}{{{#!if arg2_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg2_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg2_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg2_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg2_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg2_t!=null
{{{#4ec9b0,#6fdbba {{{type2}}}}}}
#!if arg2_t_post!=null
{{{&}}}
#!if arg2_param!=null && (arg2_kw != null || arg2_t_kw != null || arg2_t != null)
{{{ }}}
#!if arg2_param!=null
{{{#bcdce6 {{{param2}}}}}}
#!if arg3_concept != null || arg3_kw != null || arg3_t_kw != null || arg3_t != null || arg3_param != null
{{{, }}}
#!if arg3_concept!=null
##======================================= argument 3
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg3_concept_params!=null
{{{<}}}{{{#!if arg3_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg3_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg3_concept_tparam3!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg3_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg3_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg3_t!=null
{{{#4ec9b0,#6fdbba {{{type3}}}}}}
#!if arg3_t_post!=null
{{{&}}}
#!if arg3_param!=null && (arg3_kw != null || arg3_t_kw != null || arg3_t != null)
{{{ }}}
#!if arg3_param!=null
{{{#bcdce6 {{{param3}}}}}}
#!if arg4_concept != null || arg4_kw != null || arg4_t_kw != null || arg4_t != null || arg4_param != null
{{{, }}}
#!if arg4_concept!=null
##======================================= argument4
'''{{{#4ec9b0,#6fdbba {{{concept3}}}}}}'''{{{#!if arg4_concept_params!=null
{{{<}}}{{{#!if arg4_concept_tparam1!=null
{{{#4ec9b0,#6fdbba {{{T1}}}}}}}}}{{{#!if arg4_concept_tparam2!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T2}}}}}}}}}{{{#!if arg4_concept_tparam4!=null
{{{, }}}{{{#4ec9b0,#6fdbba {{{T3}}}}}}}}}{{{>}}}}}}{{{ }}}
#!if arg4_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{const }}}}}}'''
#!if arg4_t_kw!=null
'''{{{#569cd6,#CornFlowerBlue {{{int }}}}}}'''
#!if arg4_t!=null
{{{#4ec9b0,#6fdbba {{{type4}}}}}}
#!if arg4_t_post!=null
{{{&}}}
#!if arg4_param!=null && (arg4_kw != null || arg4_t_kw != null || arg4_t != null)
{{{ }}}
#!if arg4_param!=null
{{{#bcdce6 {{{param4}}}}}}
#!if arg5_param!=null
##======================================= argument5, argument6
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg6_param != null
{{{, }}}{{{#bcdce6 {{{param5}}}}}}
#!if arg_last_dots != null
{{{, ...}}}
#!if body_bclose!=null
##=======================================
{{{)}}}
#!if body_lnb!=null
[br]
#!if body_lnb!=null
{{{ }}}
#!if body_spec1!=null
'''{{{#DodgerBlue,#CornFlowerBlue {{{ const}}}}}}'''
#!if body_spec1_paren != null
{{{(}}}
#!if body_spec1_ref!=null
{{{&}}}
#!if body_spec2!=null
{{{#!if body_spec1!=null && body_spec1_paren == null
{{{ }}}}}}'''{{{#DodgerBlue,#CornFlowerBlue {{{noexcept}}}}}}'''
#!if body_spec2_paren != null
{{{(}}}
#!if body_spec2_label != null
{{{...}}}
#!if body_spec2_paren != null
{{{)}}}
#!if body_spec1_paren != null
{{{)}}}
#!if body_tmpclose!=null
##======================================= last labels
{{{>}}}
#!if label_last!=null
{{{}}}
#!if last!=null
{{{;}}}
||
| |||||||||||
<bgcolor=#20b580> | |||||||||||
| |||||||||||
설명. | |||||||||||
<bgcolor=#20b580> | |||||||||||
| C++11 | ||||||||||
설명. | |||||||||||
<bgcolor=#20b580> | |||||||||||
| C++20 | ||||||||||
설명. | |||||||||||
<bgcolor=#20b580> |
std::atomic<T>
클래스는 지정한 방식에 따라 자동으로 메모리 장벽을 세운다. 또한 각 메모리 접근에 대해 원자적 연산을 수행한다.6.1. 복사와 이동
생성자, 소멸자7. 예제 1: 사칙연산
7.1. store & load
7.2. fetch
7.3. 경쟁 상태 해소
#!syntax cpp
하나의 숫자 변수를 원자적 연산을 써서 다수의 스레드에서 증분하는 예제.- <C++ 예제 보기>
#!syntax cpp
8. 예제 2: 생산자 & 소비자 패턴
스레드를 사용한 동기화9. 예제 3: 스핀 락 (Spin Lock)
#!syntax cpp
class SpinLock
{
public:
constexpr SpinLock() noexcept = default;
~SpinLock() noexcept = default;
// const 메서드이지만 myState를 수정하고 있다.
void Lock(std::memory_order model = std::memory_order::memory_order_acquire) const volatile noexcept
{
while (!TryLock(model));
}
bool TryLock(std::memory_order model = std::memory_order::memory_order_relaxed) const volatile noexcept
{
return !mySwitch.test_and_set(model);
}
void Unlock(std::memory_order model = std::memory_order::memory_order_release) const volatile noexcept
{
mySwitch.clear(model);
}
[[nodiscard]] bool IsLocked() const noexcept
{
return mySwitch.test(std::memory_order::memory_order_relaxed);
}
SpinLock(const SpinLock&) = delete;
SpinLock& operator=(const SpinLock&) = delete;
private:
mutable volatile std::atomic_flag mySwitch;
};
// const여도 아무 문제 없다.
const SpinLock globalLock{};
size_t globalCounter = 0;
void Incrementor(size_t max) noexcept
{
for (size_t i = 0; i < max; ++i)
{
globalLock.Lock();
globalCounter++;
globalLock.Unlock();
}
}
int main()
{
const size_t target = 1000'0000'0000'0000;
const size_t thrd_count = 10;
const size_t thrd_workout = target / thrd_count;
std::vector<std::thread> myThreads{};
myThreads.reserve(thrd_count);
for (size_t i = 0; i < thrd_count; ++i)
{
myThreads.emplace(Incrementor, thrd_workout);
}
for (auto& th : myThreads)
{
th.join();
}
std::println("결과: {}", globalCounter); // 1000000000000000
return 0;
}
상기 코드는 스핀 락의 구현과 스핀 락으로 임계 영역 변수에 더하는 예시를 보여주고 있다.10. 예제 4: 스레드 대기와 기상
10.1. std::atomic_flag
10.2. 예제 5: 스레드 동기화
11. compare_exchange
11.1. 예제 6: 병렬 작업 관리자
이 문단에서는 다수의 작업을 동시에 처리할 수 있는 동질성 스레드 구현을 살펴볼 것이다.#!syntax cpp
constexpr size_t THREAD_COUNT = 4;
std::stop_source globalCancellation;
std::random_device device;
std::default_random_engine engine(device());
std::latch openingWaiter(THREAD_COUNT);
std::atomic_int tasksWaiter;
std::atomic_int activeWorkerNumber;
struct Object
{
std::atomic_int32_t refCount;
std::atomic<Object*> nextNode;
};
std::array<Object, 10> globalObjects{};
enum class TaskCat : int
{
None = 0,
AddRef,
Release,
ChangeNode,
RandomOp,
Quit
};
void Routine(std::stop_token cancellation, class WorkUnit& inst);
void TaskOnAddRef(class WorkUnit& inst, int repeat);
void TaskOnRelease(class WorkUnit& inst, int repeat);
void TaskOnChangeNode(class WorkUnit& inst, int repeat);
void TaskOnRandomOp(class WorkUnit& inst, int repeat);
예제 구현에 필요한 선언은 위와 같다.#!syntax cpp
class WorkUnit
{
public:
friend void Routine(std::stop_token cancellation, WorkUnit& inst)
{
std::println("{} 시작", inst.MakeLabel());
openingWaiter.count_down();
while (!cancellation.stop_requested())
{
if (globalCancellation.stop_requested())
{
break;
}
auto& workref = inst.myWork;
workref.wait(0, std::memory_order_acquire);
auto op = inst.GetWorkType();
auto code = inst.GetWorkCode();
switch (op)
{
case TaskCat::AddRef:
{
TaskOnAddRef(inst, code);
}
break;
case TaskCat::Release:
{
TaskOnRelease(inst, code);
}
break;
case TaskCat::ChangeNode:
{
TaskOnChangeNode(inst, code);
}
break;
case TaskCat::RandomOp:
{
TaskOnRandomOp(inst, code);
}
break;
case TaskCat::Quit:
{
activeWorkerNumber.fetch_sub(1, std::memory_order_release);
inst.isActive.store(false, std::memory_order_release);
}
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
workref.store(0, std::memory_order_release);
tasksWaiter.fetch_sub(1, std::memory_order_release);
}
std::println("{} 종료", inst.MakeLabel());
}
WorkUnit(int id) noexcept
: myId(id), myHandle(), myWork(), isActive(true)
{
}
void Start()
{
myHandle = std::jthread(Routine, std::ref(*this));
}
void Stop() noexcept
{
myHandle.request_stop();
SendMessage(TaskCat::Quit, 0);
}
void SendMessage(TaskCat op, int code)
{
// 상위 32비트: 코드, 하위 32비트: 작업 종류
const long long cmd = (static_cast<long long>(code) << 32LL) | static_cast<long long>(op);
myWork.store(cmd, std::memory_order_release);
myWork.notify_all();
}
[[nodiscard]]
TaskCat GetWorkType() const noexcept
{
return static_cast<TaskCat>(myWork.load(std::memory_order_relaxed) & 0xFFFFFFFFLL);
}
[[nodiscard]]
int GetWorkCode() const noexcept
{
return static_cast<int>(myWork.load(std::memory_order_relaxed) >> 32LL);
}
[[nodiscard]]
std::string MakeLabel() const
{
// C++23
return std::format("작업자 {}: {}", myId, std::this_thread::get_id());
}
private:
int myId;
std::jthread myHandle;
std::atomic<long long> myWork;
std::atomic_bool isActive;
};
클래스 `WorkUnit`
의 구현은 위와 같다.- <C++ 예제 보기>
#!syntax cpp void TaskOnAddRef(WorkUnit& inst, int repeat) { std::uniform_int_distribution object_dist(0, 9); std::uniform_int_distribution repeat_dist(1, repeat); for (int i = repeat_dist(engine); 0 < i; --i) { const int idx = object_dist(engine); const int cnt = globalObjects[idx].refCount.fetch_add(1, std::memory_order_acq_rel); std::println("[{}] Object[{}]의 참조 카운터가 {}로 증가함.", inst.MakeLabel(), idx, cnt + 1); } }
`TaskOnAddRef`
함수의 구현은 위와 같다.- <C++ 예제 보기>
#!syntax cpp void TaskOnRelease(WorkUnit& inst, int code) { std::uniform_int_distribution object_dist(0, 9); std::uniform_int_distribution repeat_dist(1, code); for (int i = repeat_dist(engine); 0 < i; --i) { const int idx = object_dist(engine); const int cnt = globalObjects[idx].refCount.fetch_sub(1, std::memory_order_acq_rel); std::println("[{}] Object[{}]의 참조 카운터가 {}로 감소함.", inst.MakeLabel(), idx, cnt - 1); } }
`TaskOnRelease`
함수의 구현은 위와 같다.- <C++ 예제 보기>
#!syntax cpp void TaskOnChangeNode(WorkUnit& inst, int code) { std::uniform_int_distribution object_dist(0, 9); std::uniform_int_distribution repeat_dist(1, code); for (int i = repeat_dist(engine); 0 < i; --i) { while (true) { const int src = object_dist(engine); const int dst = object_dist(engine); if (src != dst) { Object* expected = globalObjects[src].nextNode.load(std::memory_order_acquire); while (!globalObjects[src].nextNode.compare_exchange_weak(expected, &globalObjects[dst], std::memory_order_acq_rel)); globalObjects[dst].refCount.fetch_add(1, std::memory_order_acq_rel); std::println("[{}] Object[{}]의 노드 변수가 Object[{}]로 변경됨.", inst.MakeLabel(), src, dst); break; } } } }
`TaskOnChangeNode`
함수의 구현은 위와 같다.- <C++ 예제 보기>
#!syntax cpp void TaskOnRandomOp(WorkUnit& inst, int repeat) { std::uniform_int_distribution work_dist(1, 3); for (int i = 0; i < repeat; ++i) { const int op = work_dist(engine); switch (op) { case 1: { TaskOnAddRef(inst, 1); } break; case 2: { TaskOnRelease(inst, 1); } break; case 3: { TaskOnChangeNode(inst, 1); } break; } } }
`TaskOnRandomOp`
함수의 구현은 위와 같다.- <C++ 예제 보기>
#!syntax cpp int main() { std::array workerPool { WorkUnit(0), WorkUnit(1), WorkUnit(2), WorkUnit(3) }; std::println("프로그램 시작"); for (auto& worker : workerPool) { worker.Start(); } activeWorkerNumber.store(THREAD_COUNT); openingWaiter.wait(); char cmd[8]{}; while (!globalCancellation.stop_requested()) { // 작업 완료 대기 while (0 < tasksWaiter.load(std::memory_order_acquire)); if (activeWorkerNumber.load(std::memory_order_acquire) == 0) { break; } std::println("=================================================="); std::println("2자 이하의 명령어를 입력하세요. (N은 0~3 사이의 정수)"); std::println("[iN : N번 작업자의 thread::id 출력]"); std::println("[qN : N번 작업자 정지]"); std::println("[qA : 모든 작업자 정지]"); std::println("[aN : 모든 작업자가 N번까지 무작위 `Object` 원소의 참조 카운터를 증가시킴]"); std::println("[sN : 모든 작업자가 N번까지 무작위 `Object` 원소의 참조 카운터를 감소시킴]"); std::println("[dN : 모든 작업자가 N번까지 무작위 `Object` 원소의 노드 변수를 다른 노드로 바꾸고, 바꾼 노드의 참조 카운터를 증가시킴]"); std::println("[fN : 모든 작업자가 N번까지 a, s, d 명령을 무작위로 실행함]"); std::println("=================================================="); std::cin >> cmd; const int op = std::toupper(cmd[0]); const int code = cmd[1]; switch (op) { case 'I': { const int target = code - '0'; if (0 <= target and target < 4) { std::println("{}", workerPool[target].MakeLabel()); } else { std::println("잘못된 명령입니다."); } } break; case 'Q': { if (const int target = code - '0'; 0 <= target and target < 4) { // 배리어를 하나만 대기하게끔 초기화 tasksWaiter.store(1, std::memory_order_release); workerPool[target].Stop(); } else if (std::isalpha(code) and std::toupper(code) == 'A') { // 배리어를 초기화 tasksWaiter.store(activeWorkerNumber.load(std::memory_order_acquire), std::memory_order_release); globalCancellation.request_stop(); for (auto& worker : workerPool) { worker.Stop(); } } else { std::println("잘못된 명령입니다."); } } break; case 'A': { if (const int count = code - '0'; 0 < count) { // 배리어를 초기화 tasksWaiter.store(activeWorkerNumber.load(std::memory_order_acquire), std::memory_order_release); for (auto& worker : workerPool) { worker.SendMessage(TaskCat::AddRef, count); } } else { std::println("잘못된 명령입니다."); } } break; case 'S': { if (const int count = code - '0'; 0 < count) { // 배리어를 초기화 tasksWaiter.store(activeWorkerNumber.load(std::memory_order_acquire), std::memory_order_release); for (auto& worker : workerPool) { worker.SendMessage(TaskCat::Release, count); } } else { std::println("잘못된 명령입니다."); } } break; case 'D': { if (const int count = code - '0'; 0 < count) { // 배리어를 초기화 tasksWaiter.store(activeWorkerNumber.load(std::memory_order_acquire), std::memory_order_release); for (auto& worker : workerPool) { worker.SendMessage(TaskCat::ChangeNode, count); } } else { std::println("잘못된 명령입니다."); } } break; case 'F': { if (const int count = code - '0'; 0 < count) { // 배리어를 초기화 tasksWaiter.store(activeWorkerNumber.load(std::memory_order_acquire), std::memory_order_release); for (auto& worker : workerPool) { worker.SendMessage(TaskCat::RandomOp, count); } } else { std::println("잘못된 명령입니다."); } } break; default: { std::println("잘못된 명령입니다."); } break; } // 무한 반복 cmd[0] = 0; cmd[1] = 4; } std::println("프로그램 종료"); }
main
함수의 구현은 위와 같다. 상기 코드는 모두 뮤텍스를 비롯하여 운영체제 호출을 전혀 쓰지 않고 스레드 간의 동기화를 구현했다. std::atomic<T>
의 멤버 함수 wait
과 notify_all
을 써서 스레드에 명령을 내리는 방법을 보여주고 있다.12. 외부 링크
- https://www.open-std.org/JTC1/SC22/WG21/에서 찾을 수 있는 제안서 링크
- https://en.cppreference.com/w/cpp/standard_library.html에서 찾을 수 있는 모듈 링크 및 Defect Report 링크
- CLang, GNUC, MSVC의 구현 링크
==# 코드 요약 #==
#!syntax cpp
namespace std
{
enum class memory_order : /* unspecified */;
inline constexpr memory_order memory_order_relaxed = memory_order::relaxed;
inline constexpr memory_order memory_order_consume = memory_order::consume;
inline constexpr memory_order memory_order_acquire = memory_order::acquire;
inline constexpr memory_order memory_order_release = memory_order::release;
inline constexpr memory_order memory_order_acq_rel = memory_order::acq_rel;
inline constexpr memory_order memory_order_seq_cst = memory_order::seq_cst;
template<class T>
T kill_dependency(T y) noexcept;
// lock-free property
#define ATOMIC_BOOL_LOCK_FREE /* unspecified */
#define ATOMIC_CHAR_LOCK_FREE /* unspecified */
#define ATOMIC_CHAR8_T_LOCK_FREE /* unspecified */
#define ATOMIC_CHAR16_T_LOCK_FREE /* unspecified */
#define ATOMIC_CHAR32_T_LOCK_FREE /* unspecified */
#define ATOMIC_WCHAR_T_LOCK_FREE /* unspecified */
#define ATOMIC_SHORT_LOCK_FREE /* unspecified */
#define ATOMIC_INT_LOCK_FREE /* unspecified */
#define ATOMIC_LONG_LOCK_FREE /* unspecified */
#define ATOMIC_LLONG_LOCK_FREE /* unspecified */
#define ATOMIC_POINTER_LOCK_FREE /* unspecified */
// class template atomic_ref
template<class T> struct atomic_ref;
// partial specialization for pointers
template<class T> struct atomic_ref<T*>;
// class template atomic
template<class T> struct atomic;
// partial specialization for pointers
template<class T> struct atomic<T*>;
// non-member functions
template<class T>
bool atomic_is_lock_free(const volatile atomic<T>*) noexcept;
template<class T>
bool atomic_is_lock_free(const atomic<T>*) noexcept;
template<class T>
void atomic_store(volatile atomic<T>*, typename atomic<T>::value_type) noexcept;
template<class T>
void atomic_store(atomic<T>*, typename atomic<T>::value_type) noexcept;
template<class T>
void atomic_store_explicit(volatile atomic<T>*, typename atomic<T>::value_type, memory_order) noexcept;
template<class T>
void atomic_store_explicit(atomic<T>*, typename atomic<T>::value_type, memory_order) noexcept;
template<class T>
T atomic_load(const volatile atomic<T>*) noexcept;
template<class T>
T atomic_load(const atomic<T>*) noexcept;
template<class T>
T atomic_load_explicit(const volatile atomic<T>*, memory_order) noexcept;
template<class T>
T atomic_load_explicit(const atomic<T>*, memory_order) noexcept;
template<class T>
T atomic_exchange(volatile atomic<T>*, typename atomic<T>::value_type) noexcept;
template<class T>
T atomic_exchange(atomic<T>*, typename atomic<T>::value_type) noexcept;
template<class T>
T atomic_exchange_explicit(volatile atomic<T>*, typename atomic<T>::value_type, memory_order) noexcept;
template<class T>
T atomic_exchange_explicit(atomic<T>*, typename atomic<T>::value_type, memory_order) noexcept;
template<class T>
bool atomic_compare_exchange_weak(volatile atomic<T>*,
typename atomic<T>::value_type*,
typename atomic<T>::value_type) noexcept;
template<class T>
bool atomic_compare_exchange_weak(atomic<T>*,
typename atomic<T>::value_type*,
typename atomic<T>::value_type) noexcept;
template<class T>
bool atomic_compare_exchange_strong(volatile atomic<T>*,
typename atomic<T>::value_type*,
typename atomic<T>::value_type) noexcept;
template<class T>
bool atomic_compare_exchange_strong(atomic<T>*,
typename atomic<T>::value_type*,
typename atomic<T>::value_type) noexcept;
template<class T>
bool atomic_compare_exchange_weak_explicit(volatile atomic<T>*,
typename atomic<T>::value_type*,
typename atomic<T>::value_type,
memory_order, memory_order) noexcept;
template<class T>
bool atomic_compare_exchange_weak_explicit(atomic<T>*,
typename atomic<T>::value_type*,
typename atomic<T>::value_type,
memory_order, memory_order) noexcept;
template<class T>
bool atomic_compare_exchange_strong_explicit(volatile atomic<T>*,
typename atomic<T>::value_type*,
typename atomic<T>::value_type,
memory_order, memory_order) noexcept;
template<class T>
bool atomic_compare_exchange_strong_explicit(atomic<T>*,
typename atomic<T>::value_type*,
typename atomic<T>::value_type,
memory_order, memory_order) noexcept;
template<class T>
T atomic_fetch_add(volatile atomic<T>*, typename atomic<T>::difference_type) noexcept;
template<class T>
T atomic_fetch_add(atomic<T>*, typename atomic<T>::difference_type) noexcept;
template<class T>
T atomic_fetch_add_explicit(volatile atomic<T>*, typename atomic<T>::difference_type, memory_order) noexcept;
template<class T>
T atomic_fetch_add_explicit(atomic<T>*, typename atomic<T>::difference_type, memory_order) noexcept;
template<class T>
T atomic_fetch_sub(volatile atomic<T>*, typename atomic<T>::difference_type) noexcept;
template<class T>
T atomic_fetch_sub(atomic<T>*, typename atomic<T>::difference_type) noexcept;
template<class T>
T atomic_fetch_sub_explicit(volatile atomic<T>*, typename atomic<T>::difference_type, memory_order) noexcept;
template<class T>
T atomic_fetch_sub_explicit(atomic<T>*, typename atomic<T>::difference_type, memory_order) noexcept;
template<class T>
T atomic_fetch_and(volatile atomic<T>*, typename atomic<T>::value_type) noexcept;
template<class T>
T atomic_fetch_and(atomic<T>*, typename atomic<T>::value_type) noexcept;
template<class T>
T atomic_fetch_and_explicit(volatile atomic<T>*, typename atomic<T>::value_type, memory_order) noexcept;
template<class T>
T atomic_fetch_and_explicit(atomic<T>*, typename atomic<T>::value_type, memory_order) noexcept;
template<class T>
T atomic_fetch_or(volatile atomic<T>*, typename atomic<T>::value_type) noexcept;
template<class T>
T atomic_fetch_or(atomic<T>*, typename atomic<T>::value_type) noexcept;
template<class T>
T atomic_fetch_or_explicit(volatile atomic<T>*, typename atomic<T>::value_type, memory_order) noexcept;
template<class T>
T atomic_fetch_or_explicit(atomic<T>*, typename atomic<T>::value_type, memory_order) noexcept;
template<class T>
T atomic_fetch_xor(volatile atomic<T>*, typename atomic<T>::value_type) noexcept;
template<class T>
T atomic_fetch_xor(atomic<T>*, typename atomic<T>::value_type) noexcept;
template<class T>
T atomic_fetch_xor_explicit(volatile atomic<T>*, typename atomic<T>::value_type, memory_order) noexcept;
template<class T>
T atomic_fetch_xor_explicit(atomic<T>*, typename atomic<T>::value_type, memory_order) noexcept;
template<class T>
T atomic_fetch_max(volatile atomic<T>*, typename atomic<T>::value_type) noexcept;
template<class T>
T atomic_fetch_max(atomic<T>*, typename atomic<T>::value_type) noexcept;
template<class T>
T atomic_fetch_max_explicit(volatile atomic<T>*, typename atomic<T>::value_type, memory_order) noexcept;
template<class T>
T atomic_fetch_max_explicit(atomic<T>*, typename atomic<T>::value_type, memory_order) noexcept;
template<class T>
T atomic_fetch_min(volatile atomic<T>*, typename atomic<T>::value_type) noexcept;
template<class T>
T atomic_fetch_min(atomic<T>*, typename atomic<T>::value_type) noexcept;
template<class T>
T atomic_fetch_min_explicit(volatile atomic<T>*, typename atomic<T>::value_type, memory_order) noexcept;
template<class T>
T atomic_fetch_min_explicit(atomic<T>*, typename atomic<T>::value_type, memory_order) noexcept;
template<class T>
void atomic_wait(const volatile atomic<T>*, typename atomic<T>::value_type);
template<class T>
void atomic_wait(const atomic<T>*, typename atomic<T>::value_type);
template<class T>
void atomic_wait_explicit(const volatile atomic<T>*, typename atomic<T>::value_type, memory_order);
template<class T>
void atomic_wait_explicit(const atomic<T>*, typename atomic<T>::value_type, memory_order);
template<class T>
void atomic_notify_one(volatile atomic<T>*);
template<class T>
void atomic_notify_one(atomic<T>*);
template<class T>
void atomic_notify_all(volatile atomic<T>*);
template<class T>
void atomic_notify_all(atomic<T>*);
// type aliases
using atomic_bool = atomic<bool>;
using atomic_char = atomic<char>;
using atomic_schar = atomic<signed char>;
using atomic_uchar = atomic<unsigned char>;
using atomic_short = atomic<short>;
using atomic_ushort = atomic<unsigned short>;
using atomic_int = atomic<int>;
using atomic_uint = atomic<unsigned int>;
using atomic_long = atomic<long>;
using atomic_ulong = atomic<unsigned long>;
using atomic_llong = atomic<long long>;
using atomic_ullong = atomic<unsigned long long>;
using atomic_char8_t = atomic<char8_t>;
using atomic_char16_t = atomic<char16_t>;
using atomic_char32_t = atomic<char32_t>;
using atomic_wchar_t = atomic<wchar_t>;
using atomic_int8_t = atomic<int8_t>;
using atomic_uint8_t = atomic<uint8_t>;
using atomic_int16_t = atomic<int16_t>;
using atomic_uint16_t = atomic<uint16_t>;
using atomic_int32_t = atomic<int32_t>;
using atomic_uint32_t = atomic<uint32_t>;
using atomic_int64_t = atomic<int64_t>;
using atomic_uint64_t = atomic<uint64_t>;
using atomic_int_least8_t = atomic<int_least8_t>;
using atomic_uint_least8_t = atomic<uint_least8_t>;
using atomic_int_least16_t = atomic<int_least16_t>;
using atomic_uint_least16_t = atomic<uint_least16_t>;
using atomic_int_least32_t = atomic<int_least32_t>;
using atomic_uint_least32_t = atomic<uint_least32_t>;
using atomic_int_least64_t = atomic<int_least64_t>;
using atomic_uint_least64_t = atomic<uint_least64_t>;
using atomic_int_fast8_t = atomic<int_fast8_t>;
using atomic_uint_fast8_t = atomic<uint_fast8_t>;
using atomic_int_fast16_t = atomic<int_fast16_t>;
using atomic_uint_fast16_t = atomic<uint_fast16_t>;
using atomic_int_fast32_t = atomic<int_fast32_t>;
using atomic_uint_fast32_t = atomic<uint_fast32_t>;
using atomic_int_fast64_t = atomic<int_fast64_t>;
using atomic_uint_fast64_t = atomic<uint_fast64_t>;
using atomic_intptr_t = atomic<intptr_t>;
using atomic_uintptr_t = atomic<uintptr_t>;
using atomic_size_t = atomic<size_t>;
using atomic_ptrdiff_t = atomic<ptrdiff_t>;
using atomic_intmax_t = atomic<intmax_t>;
using atomic_uintmax_t = atomic<uintmax_t>;
using atomic_signed_lock_free = /* see description */;
using atomic_unsigned_lock_free = /* see description */;
// flag type and operations
struct atomic_flag;
bool atomic_flag_test(const volatile atomic_flag*) noexcept;
bool atomic_flag_test(const atomic_flag*) noexcept;
bool atomic_flag_test_explicit(const volatile atomic_flag*, memory_order) noexcept;
bool atomic_flag_test_explicit(const atomic_flag*, memory_order) noexcept;
bool atomic_flag_test_and_set(volatile atomic_flag*) noexcept;
bool atomic_flag_test_and_set(atomic_flag*) noexcept;
bool atomic_flag_test_and_set_explicit(volatile atomic_flag*, memory_order) noexcept;
bool atomic_flag_test_and_set_explicit(atomic_flag*, memory_order) noexcept;
void atomic_flag_clear(volatile atomic_flag*) noexcept;
void atomic_flag_clear(atomic_flag*) noexcept;
void atomic_flag_clear_explicit(volatile atomic_flag*, memory_order) noexcept;
void atomic_flag_clear_explicit(atomic_flag*, memory_order) noexcept;
void atomic_flag_wait(const volatile atomic_flag*, bool) noexcept;
void atomic_flag_wait(const atomic_flag*, bool) noexcept;
void atomic_flag_wait_explicit(const volatile atomic_flag*, bool, memory_order) noexcept;
void atomic_flag_wait_explicit(const atomic_flag*, bool, memory_order) noexcept;
void atomic_flag_notify_one(volatile atomic_flag*) noexcept;
void atomic_flag_notify_one(atomic_flag*) noexcept;
void atomic_flag_notify_all(volatile atomic_flag*) noexcept;
void atomic_flag_notify_all(atomic_flag*) noexcept;
// fences
extern "C" void atomic_thread_fence(memory_order) noexcept;
extern "C" void atomic_signal_fence(memory_order) noexcept;
template<class T>
struct atomic
{
using value_type = T;
static constexpr bool is_always_lock_free = /* implementation-defined */;
bool is_lock_free() const volatile noexcept;
bool is_lock_free() const noexcept;
// operations on atomic types
constexpr atomic() noexcept(is_nothrow_default_constructible_v<T>);
constexpr atomic(T) noexcept;
atomic(const atomic&) = delete;
atomic& operator=(const atomic&) = delete;
atomic& operator=(const atomic&) volatile = delete;
T load(memory_order = memory_order::seq_cst) const volatile noexcept;
T load(memory_order = memory_order::seq_cst) const noexcept;
operator T() const volatile noexcept;
operator T() const noexcept;
void store(T, memory_order = memory_order::seq_cst) volatile noexcept;
void store(T, memory_order = memory_order::seq_cst) noexcept;
T operator=(T) volatile noexcept;
T operator=(T) noexcept;
T exchange(T, memory_order = memory_order::seq_cst) volatile noexcept;
T exchange(T, memory_order = memory_order::seq_cst) noexcept;
bool compare_exchange_weak(T&, T, memory_order, memory_order) volatile noexcept;
bool compare_exchange_weak(T&, T, memory_order, memory_order) noexcept;
bool compare_exchange_strong(T&, T, memory_order, memory_order) volatile noexcept;
bool compare_exchange_strong(T&, T, memory_order, memory_order) noexcept;
bool compare_exchange_weak(T&, T, memory_order = memory_order::seq_cst) volatile noexcept;
bool compare_exchange_weak(T&, T, memory_order = memory_order::seq_cst) noexcept;
bool compare_exchange_strong(T&, T, memory_order = memory_order::seq_cst) volatile noexcept;
bool compare_exchange_strong(T&, T, memory_order = memory_order::seq_cst) noexcept;
void wait(T, memory_order = memory_order::seq_cst) const volatile noexcept;
void wait(T, memory_order = memory_order::seq_cst) const noexcept;
void notify_one() volatile noexcept;
void notify_one() noexcept;
void notify_all() volatile noexcept;
void notify_all() noexcept;
};
template<class T>
struct atomic_ref
{
private:
T* ptr; // exposition only
public:
using value_type = T;
static constexpr size_t required_alignment = /* implementation-defined */;
static constexpr bool is_always_lock_free = /* implementation-defined */;
bool is_lock_free() const noexcept;
explicit atomic_ref(T&);
atomic_ref(const atomic_ref&) noexcept;
atomic_ref& operator=(const atomic_ref&) = delete;
void store(T, memory_order = memory_order_seq_cst) const noexcept;
T operator=(T) const noexcept;
T load(memory_order = memory_order_seq_cst) const noexcept;
operator T() const noexcept;
T exchange(T, memory_order = memory_order_seq_cst) const noexcept;
bool compare_exchange_weak(T&, T, memory_order, memory_order) const noexcept;
bool compare_exchange_strong(T&, T, memory_order, memory_order) const noexcept;
bool compare_exchange_weak(T&, T, memory_order = memory_order_seq_cst) const noexcept;
bool compare_exchange_strong(T&, T, memory_order = memory_order_seq_cst) const noexcept;
void wait(T, memory_order = memory_order::seq_cst) const noexcept;
void notify_one() const noexcept;
void notify_all() const noexcept;
};
struct atomic_flag
{
constexpr atomic_flag() noexcept;
atomic_flag(const atomic_flag&) = delete;
atomic_flag& operator=(const atomic_flag&) = delete;
atomic_flag& operator=(const atomic_flag&) volatile = delete;
bool test(memory_order = memory_order::seq_cst) const volatile noexcept;
bool test(memory_order = memory_order::seq_cst) const noexcept;
bool test_and_set(memory_order = memory_order::seq_cst) volatile noexcept;
bool test_and_set(memory_order = memory_order::seq_cst) noexcept;
void clear(memory_order = memory_order::seq_cst) volatile noexcept;
void clear(memory_order = memory_order::seq_cst) noexcept;
void wait(bool, memory_order = memory_order::seq_cst) const volatile noexcept;
void wait(bool, memory_order = memory_order::seq_cst) const noexcept;
void notify_one() volatile noexcept;
void notify_one() noexcept;
void notify_all() volatile noexcept;
void notify_all() noexcept;
};
}
13. 둘러보기
||<:><-12><width=90%><tablewidth=80%><tablebordercolor=#20b580><rowbgcolor=#090f0a,#050b09><rowcolor=#d7d7d7,#a1a1a1>C++||||
}}}
}}}||
}}}||
C언어와의 차이점 | 학습 자료 | 평가 | |||||||||||||
<bgcolor=#20b580> | |||||||||||||||
<rowcolor=#090912,#bebebf>C++ 문법 | |||||||||||||||
<bgcolor=#ffffff> | |||||||||||||||
main | 헤더 | 모듈 | |||||||||||||
함수 | 구조체 | 이름공간 | |||||||||||||
한정자 | 참조자 | 포인터 | |||||||||||||
클래스 | 값 범주론 | 특성 | |||||||||||||
auto | using | decltype | |||||||||||||
상수 표현식 | 람다 표현식 | 객체 이름 검색 | |||||||||||||
템플릿 | 템플릿 제약조건 | 메타 프로그래밍 | |||||||||||||
<bgcolor=#20b580> | |||||||||||||||
<rowcolor=#090912,#bebebf>C++ 버전 | |||||||||||||||
<bgcolor=#ffffff> | |||||||||||||||
C++26 | C++23 | C++20 | |||||||||||||
C++17 | C++14 | C++11 | |||||||||||||
C++03 | C++98 | C with Classes | |||||||||||||
<bgcolor=#20b580> | |||||||||||||||
<rowcolor=#090912,#bebebf>C++ 표준 라이브러리 | |||||||||||||||
<rowcolor=#090912,#bebebf>문서가 있는 모듈 목록 | |||||||||||||||
<bgcolor=#ffffff> | |||||||||||||||
#개요 | C++11 #개요 | <unordered_map> C++11 #개요 | |||||||||||||
C++20 #개요 | #개요 | #개요 | |||||||||||||
C++11 #개요 | C++11 #개요 | C++17 #개요 | |||||||||||||
#개요 | <string_view> C++17 #개요 | C++20 #개요 | |||||||||||||
C++11 #개요 | C++11 #개요 | C++11 #개요 | |||||||||||||
C++20 #개요 | C++23 #개요 | ||||||||||||||
<bgcolor=#20b580> | |||||||||||||||
<rowcolor=#090912,#bebebf>예제 목록 | |||||||||||||||
<bgcolor=#ffffff> | |||||||||||||||
{{{#!wiki style=""text-align: center, margin: 0 -10px" {{{#!folding [ 펼치기 · 접기 ] | 동기화 전략 1 임계 영역과 상호 배제 | 동기화 전략 2 원자적 연산과 메모리 장벽 | 동시성 자료 구조 1 큐 구현 | 동시성 자료 구조 2 집합 구현 | |||||||||||
함수 템플릿 일반화 프로그래밍 | 전이 참조 완벽한 매개변수 전달 | 튜플 구현 가변 클래스 템플릿 | 직렬화 함수 구현 템플릿 매개변수 묶음 | ||||||||||||
SFINAE 1 멤버 함수 검사 | SFINAE 2 자료형 태그 검사 | SFINAE 3 메타 데이터 | SFINAE 4 자료형 트레잇 | ||||||||||||
제약조건 1 개념 (Concept) | 제약조건 2 상속 여부 검사 | 제약조건 3 클래스 명세 파헤치기 | 제약조건 4 튜플로 함자 실행하기 | ||||||||||||
메타 프로그래밍 1 특수화 여부 검사 | 메타 프로그래밍 2 컴파일 시점 문자열 | 메타 프로그래밍 3 자료형 리스트 | 메타 프로그래밍 4 안전한 union |
}}}||
<bgcolor=#20b580> | |||||||||||||||
<rowcolor=#090912,#bebebf>외부 링크 | |||||||||||||||
<bgcolor=#ffffff> | |||||||||||||||
{{{#!wiki style=""text-align: center, margin: 0 -10px" | |||||||||||||||
<bgcolor=#20b580> | |||||||||||
<rowcolor=#090912,#bebebf>C++ |
[1] 99% 이상[2] 앞서 살펴본 예제에서는 뮤텍스로 이를 해결했었다.[3] 다만 원자적 연산도 하나의 메모리 블록을 잠그는 (lock add 등의 기계어 명령어가 쓰임) 동작이 필요하므로 일반적인 메모리 접근보다 느리다는 사실을 알아 두어야 한다.