Programming

std :: atomic은 정확히 무엇입니까?

procodes 2020. 7. 17. 21:26
반응형

std :: atomic은 정확히 무엇입니까?


나는 그것이 std::atomic<>원자 적 객체 라는 것을 이해 합니다. 그러나 어느 정도까지 원자? 내 이해에 따르면 작업은 원자가 될 수 있습니다. 물체를 원자로 만드는 것은 정확히 무엇을 의미합니까? 예를 들어 다음 코드를 동시에 실행하는 두 개의 스레드가있는 경우 :

a = a + 12;

그렇다면 전체 작업이 add_twelve_to(int)원 자성입니까? 아니면 변수 원자가 변경 operator=()되었습니까?


std :: atomic <> 의 각 인스턴스화 및 전체 특수화는 정의되지 않은 동작을 발생시키지 않고 다른 스레드가 동시에 작동 할 수있는 유형을 나타냅니다.

원자 유형의 객체는 데이터 경쟁이없는 유일한 C ++ 객체입니다. 즉, 한 스레드가 원자 개체에 쓰는 동안 다른 스레드가이를 읽는 경우 동작이 잘 정의되어 있습니다.

또한 원자 객체에 대한 액세스는 스레드 간 동기화를 설정하고로 지정된 비 원자 메모리 액세스를 주문할 수 std::memory_order있습니다.

std::atomic<>C ++ 이전 11 번 랩핑 작업 은 GCC의 경우 MSVC 또는 원자 적 부틴(예를 들어) 연동 기능사용하여 수행해야했습니다 .

또한 동기화 및 순서 제약 조건을 지정 std::atomic<>하는 다양한 메모리 순서허용하여 더 많은 제어 기능을 제공합니다 . C ++ 11 원자 및 메모리 모델에 대한 자세한 내용을 보려면 다음 링크가 유용 할 수 있습니다.

일반적인 사용 사례의 경우 오버로드 된 산술 연산자 또는 다른 세트를 사용합니다 .

std::atomic<long> value(0);
value++; //This is an atomic op
value += 5; //And so is this

연산자 구문을 사용하면 메모리 순서를 지정할 수 없으므로 std::memory_order_seq_cstC ++ 11의 모든 원자 연산에 대한 기본 순서이므로이 연산은로 수행됩니다. 모든 원자 연산 간의 순차 일관성 (전체 전역 순서)을 보장합니다.

그러나 경우에 따라이 작업이 필요하지 않을 수도 있고 무료로 제공되지 않을 수도 있으므로보다 명시적인 형식을 사용하는 것이 좋습니다.

std::atomic<long> value {0};
value.fetch_add(1, std::memory_order_relaxed); // Atomic, but there are no synchronization or ordering constraints
value.fetch_add(5, std::memory_order_release); // Atomic, performs 'release' operation

자, 당신의 예 :

a = a + 12;

단일 원자 연산으로 계산하지 않을 것이다 : 그것은 초래한다 a.load()(자체 원자이다)이 값 사이, 덧셈 12a.store()최종 결과 (도 원 참조). 앞서 언급했듯이 std::memory_order_seq_cst여기에서 사용됩니다.

그러나을 쓰면 a += 12원자 연산 (앞서 언급했듯이)과 거의 같습니다 a.fetch_add(12, std::memory_order_seq_cst).

귀하의 의견에 관해서는 :

레귤러 int에는 원자로드 및 저장이 있습니다. 그것을 감싸는 요점은 atomic<>무엇입니까?

귀하의 진술은 상점 및 / 또는로드에 원 자성을 보장하는 아키텍처에만 적용됩니다. 이를 수행하지 않는 아키텍처가 있습니다. 또한 일반적으로 워드 / 워드 정렬 주소에서 작업을 수행해야하므로 원 자성 std::atomic<>추가 요구 사항없이 모든 플랫폼에서 원 자성으로 보장됩니다 . 또한 다음과 같은 코드를 작성할 수 있습니다.

void* sharedData = nullptr;
std::atomic<int> ready_flag = 0;

// Thread 1
void produce()
{
    sharedData = generateData();
    ready_flag.store(1, std::memory_order_release);
}

// Thread 2
void consume()
{
    while (ready_flag.load(std::memory_order_acquire) == 0)
    {
        std::this_thread::yield();
    }

    assert(sharedData != nullptr); // will never trigger
    processData(sharedData);
}

Note that assertion condition will always be true (and thus, will never trigger), so you can always be sure that data is ready after while loop exits. That is because:

  • store() to the flag is performed after sharedData is set (we assume that generateData() always returns something useful, in particular, never returns NULL) and uses std::memory_order_release order:

memory_order_release

A store operation with this memory order performs the release operation: no reads or writes in the current thread can be reordered after this store. All writes in the current thread are visible in other threads that acquire the same atomic variable

  • sharedData is used after while loop exits, and thus after load() from flag will return a non-zero value. load() uses std::memory_order_acquire order:

std::memory_order_acquire

A load operation with this memory order performs the acquire operation on the affected memory location: no reads or writes in the current thread can be reordered before this load. All writes in other threads that release the same atomic variable are visible in the current thread.

This gives you precise control over the synchronization and allows you to explicitly specify how your code may/may not/will/will not behave. This would not be possible if only guarantee was the atomicity itself. Especially when it comes to very interesting sync models like the release-consume ordering.


I understand that std::atomic<> makes an object atomic.

That's a matter of perspective... you can't apply it to arbitrary objects and have their operations become atomic, but the provided specialisations for (most) integral types and pointers can be used.

a = a + 12;

std::atomic<> does not (use template expressions to) simplify this to a single atomic operation, instead the operator T() const volatile noexcept member does an atomic load() of a, then twelve is added, and operator=(T t) noexcept does a store(t).

참고URL : https://stackoverflow.com/questions/31978324/what-exactly-is-stdatomic

반응형