C ++ 11의 std :: atomic :: compare_exchange_weak () 이해


88
bool compare_exchange_weak (T& expected, T val, ..);

compare_exchange_weak()C ++ 11에서 제공되는 비교-교환 프리미티브 중 하나입니다. 객체의 값이 같더라도 false를 반환한다는 점 에서 합니다 expected. 이는 일련의 명령어 (x86의 명령어 대신)가이를 구현하는 데 사용되는 일부 플랫폼의 스퓨리어스 오류 때문입니다. 이러한 플랫폼에서 컨텍스트 전환, 다른 스레드에 의한 동일한 주소 (또는 캐시 라인) 다시로드 등은 기본 요소에 실패 할 수 있습니다. 그건 spurious는 (동일하지 않은 객체의 값이 아니다로서 expected동작 실패). 대신 일종의 타이밍 문제입니다.

그러나 나를 당혹스럽게하는 것은 C ++ 11 표준 (ISO / IEC 14882)에서 말한 것입니다.

29.6.5 .. 스퓨리어스 실패의 결과는 약한 비교 및 ​​교환의 거의 모든 사용이 루프에있게된다는 것입니다.

거의 모든 용도 에서 루프에 있어야하는 이유는 무엇 입니까? 이것은 가짜 실패로 인해 실패 할 때 루프를 반복한다는 의미입니까? 그렇다면 compare_exchange_weak()루프를 직접 사용 하고 작성 해야하는 이유는 무엇입니까? 우리는 compare_exchange_strong()가짜 실패를 제거해야한다고 생각하는 것을 사용할 수 있습니다 . 의 일반적인 사용 사례는 compare_exchange_weak()무엇입니까?

또 다른 질문이 있습니다. 그의 저서 "C ++ Concurrency In Action"에서 Anthony는 다음과 같이 말합니다.

//Because compare_exchange_weak() can fail spuriously, it must typically
//be used in a loop:

bool expected=false;
extern atomic<bool> b; // set somewhere else
while(!b.compare_exchange_weak(expected,true) && !expected);

//In this case, you keep looping as long as expected is still false,
//indicating that the compare_exchange_weak() call failed spuriously.

!expected루프 상태에있는 이유는 무엇 입니까? 모든 스레드가 굶어 죽고 한동안 진행되지 않도록 방지하기 위해 있습니까?

편집 : (마지막 질문 하나)

단일 하드웨어 CAS 명령어가없는 플랫폼에서는 약한 버전과 강력한 버전이 모두 LL / SC (예 : ARM, PowerPC 등)를 사용하여 구현됩니다. 그렇다면 다음 두 루프 사이에 차이점이 있습니까? 이유가 있다면? (나에게 비슷한 성능을 가져야합니다.)

// use LL/SC (or CAS on x86) and ignore/loop on spurious failures
while (!compare_exchange_weak(..))
{ .. }

// use LL/SC (or CAS on x86) and ignore/loop on spurious failures
while (!compare_exchange_strong(..)) 
{ .. }

여러분 모두가 루프 내부에 성능 차이가있을 수 있다고 언급하는 마지막 질문을 드리겠습니다. C ++ 11 표준 (ISO / IEC 14882)에서도 언급됩니다.

비교 및 교환이 루프에있을 때 약한 버전은 일부 플랫폼에서 더 나은 성능을 제공합니다.

그러나 위에서 분석 한 것처럼 루프의 두 버전은 동일하거나 유사한 성능을 제공해야합니다. 내가 그리워하는 것은 무엇입니까?


4
첫 번째 질문이 있지만 많은 경우에 (강한 버전을 사용하든 약한 버전을 사용하든) 어쨌든 반복해야하며 약한 버전이 강력한 버전보다 더 나은 성능을 가질 수 있습니다.
TC

2
약한 CAS와 강한 CAS는 모두 "LL / SC를 사용하여"구현됩니다. 버블 정렬과 퀵 정렬이 "스왑을 사용하여"구현되는 것과 같은 방식입니다. 즉, 작업을 완료하는 데 사용되는 기본 작업이라는 의미에서. 그들이 포장 주위 LL / SC 매우 다르다. 약한 CAS는 LL / SC에 불과합니다. 강력한 CAS는 LL / SC와 다른 여러 가지입니다.
Sneftel 2014 년


@TuXiaomi에 해당 링크에 대한 답변이 있습니다. 표준에 명시된대로 "약한 버전이 일부 플랫폼에서 더 나은 성능을 제공 할 것"이라는 이유를 알 수 없습니다.
Deqing

@Deqing 다른 경우, compare_exchange_weak은 다른 프로세서 또는 스레드의 인터럽트 또는 작업으로 인해 가짜로 실패 할 수 있습니다. 이러한 플랫폼에서 compare_exchange_strong은 사실상 compare_exchange_weak의 루프입니다. 가짜로 실패하면 다시 루프됩니다. 도움이 되나요? 어쩌면 내가 잘못입니다
화 샤오 미

답변:


75

루프에서 교환하는 이유는 무엇입니까?

일반적으로 작업을 진행하기 전에 작업이 완료되기를 원하므로 compare_exchange_weak성공할 때까지 교환을 시도하도록 루프에 넣 습니다 (즉, 반환 true).

또한 compare_exchange_strong루프에서 자주 사용됩니다. 스퓨리어스 실패로 인해 실패하지는 않지만 동시 쓰기로 인해 실패합니다.

weak대신 사용 하는 이유 는 strong무엇입니까?

아주 쉬움 : 스퓨리어스 실패는 자주 발생하지 않으므로 성능에 큰 타격을주지 않습니다. 반대로 이러한 실패를 허용하면 일부 플랫폼 weak에서 버전을 훨씬 더 효율적으로 구현할 수 있습니다 (와 비교하여 strong). strong항상 가짜 실패를 확인하고 마스크해야합니다. 이것은 비싸다.

따라서 일부 플랫폼 weak보다 훨씬 빠르기 때문에 사용 strong됩니다.

언제 사용해야 weak언제 strong?

참조 할 때 사용하는 힌트를 언급 weak할 때 사용하는 방법과 strong:

비교 및 교환이 루프에있을 때 약한 버전은 일부 플랫폼에서 더 나은 성능을 제공합니다. 약한 비교 및 ​​교환에는 루프가 필요하고 강한 것은 필요하지 않을 때 강한 것이 바람직합니다.

따라서 대답은 기억하기 매우 간단 해 보입니다. 가짜 실패 때문에 루프를 도입해야한다면 그렇게하지 마십시오. 를 사용하십시오 strong. 어쨌든 루프가 있으면 weak.

!expected예에있는 이유

상황과 원하는 의미에 따라 다르지만 일반적으로 정확성을 위해 필요하지 않습니다. 생략하면 매우 유사한 의미가 생성됩니다. 다른 스레드가 값을로 재설정 할 수있는 경우에만 false의미 체계가 약간 다를 수 있습니다 (하지만 원하는 경우 의미있는 예제를 찾을 수 없습니다). 자세한 설명은 Tony D.의 설명을 참조하십시오.

다른 쓰레드가 쓸 때의 빠른 트랙 일 뿐이다 true. 그러면 true다시 쓰려고하는 대신 중단한다 .

마지막 질문에 대해

그러나 위에서 분석 한 것처럼 루프의 두 버전은 동일하거나 유사한 성능을 제공해야합니다. 내가 그리워하는 것은 무엇입니까?

에서 위키 백과 :

문제의 메모리 위치에 대한 동시 업데이트가없는 경우 LL / SC의 실제 구현이 항상 성공하는 것은 아닙니다. 컨텍스트 전환, 다른로드 링크 또는 (많은 플랫폼에서) 다른로드 또는 저장 작업과 같은 두 작업 간의 예외적 인 이벤트로 인해 저장 조건이 허위로 실패합니다. 메모리 버스를 통해 브로드 캐스트되는 업데이트가 있으면 이전 구현이 실패합니다.

따라서 LL / SC는 예를 들어 컨텍스트 전환에서 가짜로 실패합니다. 이제 강력한 버전은 "자신의 작은 루프"를 가져 와서 가짜 오류를 감지하고 다시 시도하여이를 숨 깁니다. 이 자체 루프는 스퓨리어스 실패 (및 마스킹)와 동시 액세스로 인한 실패 (값이 반환 됨)를 구분해야하기 때문에 일반적인 CAS 루프보다 더 복잡합니다 false. 약한 버전에는 이러한 자체 루프가 없습니다.

두 예제 모두에서 명시적인 루프를 제공하기 때문에 강력한 버전에 대해 작은 루프가 필요하지 않습니다. 결과적으로 strong버전이 있는 예 에서는 실패 확인이 두 번 수행됩니다. 한 번 compare_exchange_strong(스퓨리어스 실패와 동시 액세스를 구분해야하기 때문에 더 복잡함) 및 루프에서 한 번. 이 값 비싼 수표는 불필요하며 weak여기에서 더 빠른 이유 가 있습니다.

또한 귀하의 주장 (LL / SC) 은이를 구현할 수 있는 하나의 가능성 일뿐 입니다. 명령어 세트가 다른 플랫폼이 더 많이 있습니다. 또한 (더 중요한 것은) 가능한 모든 데이터 유형에std::atomic 대한 모든 작업을 지원해야 하므로 천만 바이트 구조체를 선언하더라도 여기에서 사용할 수 있습니다 . CAS가있는 CPU에서도 천만 바이트를 CAS 할 수 없으므로 컴파일러는 다른 명령을 생성합니다 (잠금 획득, 비 원자 비교 및 ​​스왑, 잠금 해제). 이제 천만 바이트를 교환하는 동안 얼마나 많은 일이 발생할 수 있는지 생각해보십시오. 따라서 8 바이트 교환에서는 스퓨리어스 오류가 매우 드물지만이 경우에는 더 일반적 일 수 있습니다.compare_exchange

간단히 말해서, C ++는 "최선의 노력"하나 ( weak)와 "그 사이에 얼마나 많은 나쁜 일이 일어날 지에 관계없이 확실히 할 것입니다" ( ) 라는 두 가지 의미를 제공합니다 strong. 다양한 데이터 유형과 플랫폼에서 이것이 어떻게 구현되는지는 완전히 다른 주제입니다. 특정 플랫폼의 구현에 멘탈 모델을 연결하지 마십시오. 표준 라이브러리는 사용자가 알고있는 것보다 더 많은 아키텍처에서 작동하도록 설계되었습니다. 우리가 도출 할 수있는 유일한 일반적인 결론은 성공을 보장하는 것이 실패 가능성에 대한 여지를 남기고 시도하는 것보다 일반적으로 더 어렵습니다 (따라서 추가 작업이 필요할 수 있음).


"스퓨리어스 실패를 용인 할 수없는 경우에만 strong을 사용하십시오." -동시 쓰기로 인한 실패와 스퓨리어스 실패를 구분하는 알고리즘이 실제로 있습니까? 내가 생각할 수있는 모든 것은 우리가 때때로 업데이트를 놓칠 수 있거나 어쨌든 루프가 필요하지 않은 경우입니다.
Voo

4
@Voo : 답변이 업데이트되었습니다. 이제 참조의 힌트가 포함됩니다. 구별하는 알고리즘이있을 수 있습니다. 예를 들어, "하나는 반드시 업데이트해야합니다"라는 의미를 생각해보십시오. 업데이트는 정확히 한 번만 수행되어야하므로 동시 쓰기로 인해 실패하면 다른 사람이 수행했음을 알고 중단 할 수 있습니다. 가짜 실패로 인해 실패하면 아무도 업데이트하지 않았으므로 다시 시도해야합니다.
gexicide

8
" 예제에서 왜! expected입니까? 정확성을 위해 필요하지 않습니다. 생략하면 동일한 의미가 생성됩니다." - 그리 ... 찾은 때문에 처음 교환이 실패 할 말한다면 b이미 true다음, -와 expected지금 true-없이 && !expected그것을 반복하고 또 다른 (바보) 교환을 시도 true하고 true있는 잘 하찮게으로부터 파괴 "성공"할 수 while루프, 하지만 전시 할 수 b그 사이에으로 다시 변경된 경우 의미있게 다른 동작이 발생합니다. 이 false경우 루프는 계속되고 결국 중단되기 전에 다시 설정 될 수 있습니다 . b true
Tony Delroy 2014-08-08

@TonyD : 맞습니다.
gexicide 2014-08-08

) 죄송합니다 사람, 내가 한 번 더 마지막 질문 추가
에릭 Z

18

다양한 온라인 리소스 (예 : this one and this one ), C ++ 11 Standard 및 여기에 제공된 답변을 살펴본 후 직접 답변하려고합니다 .

관련 질문이 병합되고 (예 : " 왜! expected? "가 "왜 compare_exchange_weak ()를 루프에 넣었 습니까? ") 병합되고 그에 따라 답변이 제공됩니다.


compare_exchange_weak ()이 거의 모든 용도에서 루프에 있어야하는 이유는 무엇입니까?

전형적인 패턴 A

원자 변수의 값을 기반으로 원자 업데이트를 수행해야합니다. 실패는 변수가 원하는 값으로 업데이트되지 않았으며 다시 시도하려고 함을 나타냅니다. 참고 우리가 정말 동시 쓰기 또는 가짜 오류로 인해 실패 여부에 대해 걱정하지 않는다. 그러나 우리는 이러한 변화를 만드는 것이 우리 라는 사실에 관심 이 있습니다.

expected = current.load();
do desired = function(expected);
while (!current.compare_exchange_weak(expected, desired));

실제 예는 여러 스레드가 단일 연결 목록에 요소를 동시에 추가하는 것입니다. 각 스레드는 먼저 헤드 포인터를로드하고 새 노드를 할당하고이 새 노드에 헤드를 추가합니다. 마지막으로 새 노드를 헤드와 교체하려고합니다.

또 다른 예는 std::atomic<bool>. 루프 current를 처음 설정 true하고 종료 하는 스레드에 따라 한 번에 최대 하나의 스레드가 임계 섹션에 들어갈 수 있습니다 .

전형적인 패턴 B

이것은 실제로 Anthony의 책에서 언급 된 패턴입니다. 패턴 A와는 반대로 원자 변수가 한 번 업데이트되기를 원하지만 누가 업데이트하는지는 신경 쓰지 않습니다. 업데이트되지 않은 한 다시 시도하십시오. 일반적으로 부울 변수와 함께 사용됩니다. 예를 들어 상태 머신이 계속 진행될 수 있도록 트리거를 구현해야합니다. 어떤 스레드가 방아쇠를 당기는지는 상관 없습니다.

expected = false;
// !expected: if expected is set to true by another thread, it's done!
// Otherwise, it fails spuriously and we should try again.
while (!current.compare_exchange_weak(expected, true) && !expected);

일반적으로이 패턴을 사용하여 뮤텍스를 구현할 수 없습니다. 그렇지 않으면 여러 스레드가 동시에 중요 섹션 내에있을 수 있습니다.

즉, compare_exchange_weak()루프 외부 에서 사용하는 경우는 드뭅니다 . 반대로 강력한 버전이 사용되는 경우가 있습니다. 예 :

bool criticalSection_tryEnter(lock)
{
  bool flag = false;
  return lock.compare_exchange_strong(flag, true);
}

compare_exchange_weak 가짜 실패로 인해 돌아올 때 아직 아무도 중요한 부분을 차지하지 않을 가능성이 있기 때문에 여기에서는 적절하지 않습니다.

굶주린 실?

언급 할 가치가있는 한 가지 요점은 스퓨리어스 오류가 계속 발생하여 스레드가 굶주 리면 어떻게 될까요? 이론적으로 compare_exchange_XXX()는 명령 시퀀스 (예 : LL / SC)로 구현 될 때 플랫폼에서 발생할 수 있습니다. LL과 SC간에 동일한 캐시 라인에 자주 액세스하면 연속적인 스퓨리어스 오류가 발생합니다. 보다 현실적인 예는 모든 동시 스레드가 다음과 같은 방식으로 인터리브되는 멍청한 스케줄링 때문입니다.

Time
 |  thread 1 (LL)
 |  thread 2 (LL)
 |  thread 1 (compare, SC), fails spuriously due to thread 2's LL
 |  thread 1 (LL)
 |  thread 2 (compare, SC), fails spuriously due to thread 1's LL
 |  thread 2 (LL)
 v  ..

일어날 수 있습니까?

다행히 C ++ 11에 필요한 기능 덕분에 영원히 발생하지는 않을 것입니다.

구현은 원자 객체가 예상과 다른 값을 갖거나 원자 객체에 대한 동시 수정이 있지 않는 한 약한 비교 및 ​​교환 작업이 지속적으로 false를 반환하지 않도록해야합니다.

왜 compare_exchange_weak ()을 사용하고 루프를 직접 작성해야합니까? compare_exchange_strong ()을 사용할 수 있습니다.

때에 따라 다르지.

사례 1 : 둘 다 루프 내에서 사용해야하는 경우. C ++ 11 말한다 :

비교 및 교환이 루프에있을 때 약한 버전은 일부 플랫폼에서 더 나은 성능을 제공합니다.

x86에서 (적어도 현재. 아마도 더 많은 코어가 도입되면 언젠가는 성능을 위해 LL / SC와 유사한 계획에 의지 할 것입니다), 약한 버전과 강력한 버전은 둘 다 단일 명령어로 요약되기 때문에 본질적으로 동일합니다 cmpxchg. 원자 적으로compare_exchange_XXX() 구현되지 않은 다른 플랫폼 (여기서는 단일 하드웨어 기본 요소가 없음을 의미 함)에서는 강력한 버전이 스퓨리어스 오류를 처리하고 그에 따라 재 시도해야하므로 루프 내부의 약한 버전이 전투에서 승리 할 수 ​​있습니다.

그러나,

거의, 우리는 선호하지 않을 수 compare_exchange_strong()이상 compare_exchange_weak()심지어 루프. 예를 들어 원자 변수가로드되고 계산 된 새 값이 교환되는 사이에 할 일이 많을 때 ( function()위 참조 ). 원자 변수 자체가 자주 변경되지 않으면 모든 스퓨리어스 오류에 대해 값 비싼 계산을 반복 할 필요가 없습니다. 대신, 우리는 compare_exchange_strong()그러한 실패 를 "흡수"하기를 바라며 실제 값 변경으로 인해 실패 할 때만 계산을 반복합니다.

사례 2 : compare_exchange_weak() 루프 내 에서만 사용해야하는 경우. C ++ 11은 또한 다음과 같이 말합니다.

약한 비교 및 ​​교환에는 루프가 필요하고 강한 것은 필요하지 않을 때 강한 것이 바람직합니다.

이것은 일반적으로 약한 버전에서 가짜 오류를 제거하기 위해 루프를 수행하는 경우입니다. 동시 쓰기로 인해 교환이 성공하거나 실패 할 때까지 재 시도합니다.

expected = false;
// !expected: if it fails spuriously, we should try again.
while (!current.compare_exchange_weak(expected, true) && !expected);

기껏해야 바퀴를 재발 명하고 compare_exchange_strong(). 보다 나쁜? 이 접근 방식은 하드웨어에서 가짜가 아닌 비교 및 ​​교환을 제공하는 시스템을 최대한 활용하지 못합니다 .

마지막으로, 다른 일을 반복한다면 (예 : 위의 "전형적인 패턴 A"참조) compare_exchange_strong()루프에 넣어야 할 좋은 기회가 있습니다. 이는 이전 사례로 돌아갑니다.


17

거의 모든 용도 에서 루프에 있어야하는 이유는 무엇 입니까?

반복하지 않고 실패하면 프로그램이 유용한 작업을 수행하지 않았기 때문입니다. 원자 객체를 업데이트하지 않았고 현재 값이 무엇인지 알지 못합니다 (수정 : Cameron의 아래 주석 참조). 통화가 유용한 일을하지 못한다면 그 일의 요점은 무엇입니까?

이것은 가짜 실패로 인해 실패 할 때 반복된다는 의미입니까?

예.

그렇다면 compare_exchange_weak()루프를 직접 사용 하고 작성 해야하는 이유는 무엇입니까? 우리는 가짜 실패를 제거해야한다고 생각하는 compare_exchange_strong ()을 사용할 수 있습니다. compare_exchange_weak ()의 ​​일반적인 사용 사례는 무엇입니까?

일부 아키텍처 compare_exchange_weak에서는 더 효율적이고 스퓨리어스 오류는 매우 드물게 발생하므로 약한 형식과 루프를 사용하여 더 효율적인 알고리즘을 작성할 수 있습니다.

일반적으로 스퓨리어스 오류에 대해 걱정할 필요가 없기 때문에 알고리즘이 반복 될 필요가 없으면 대신 강력한 버전을 사용하는 것이 좋습니다. 강력한 버전에서도 루프가 필요한 경우 (많은 알고리즘이 어쨌든 루프해야 함) 약한 형식을 사용하는 것이 일부 플랫폼에서 더 효율적일 수 있습니다.

!expected루프 상태에있는 이유는 무엇 입니까?

값이 true다른 스레드 에 의해 설정되었을 수 있으므로 설정을 시도하는 동안 계속 반복되는 것을 원하지 않습니다.

편집하다:

그러나 위에서 분석 한 것처럼 루프의 두 버전은 동일하거나 유사한 성능을 제공해야합니다. 내가 그리워하는 것은 무엇입니까?

스퓨리어스 실패가 가능한 플랫폼에서는 스퓨리어스 실패 compare_exchange_strong를 확인하고 재 시도하기 위해 구현 이 더 복잡 해야한다는 것은 분명합니다 .

약한 형식은 가짜 실패시 반환되며 재 시도하지 않습니다.


2
+1 모든 카운트에서 사실적으로 정확합니다 (Q가 절실하게 필요로하는).
Tony Delroy 2014-08-08

정보 you don't know what its current value is가짜 오류가 발생할 때 1 점에서, 현재의 값은 그 순간의 기대 값과 동일하지 않겠습니까? 그렇지 않으면 실제 실패가 될 것입니다.
Eric Z

IMO, 약한 버전과 강력한 버전은 모두 단일 CAS 하드웨어 기본 요소가없는 플랫폼에서 LL / SC를 사용하여 구현됩니다. 그래서 나에게 왜 while(!compare_exchange_weak(..))와 사이에 성능 차이가 while(!compare_exchange_strong(..))있습니까?
Eric Z

죄송합니다. 마지막 질문을 하나 더 추가했습니다.
Eric Z

1
@ 조나단 : 그냥 nitpick,하지만 당신은 할 수 는 외관상 실패 할 경우 현재의 값을 알고있다 (물론 그건 당신이 변수를 읽을 시간을 기준으로 현재 값이 완전히 다른 문제를 여전히 여부,하지만 상관없이 약한 / 강한의입니다). 예를 들어이 값을 사용하여 값이 null이라고 가정하고 변수를 설정하려고 시도하고 실패 할 경우 (가짜 여부) 계속 시도하지만 실제 값에 따라 달라집니다.
카메론

13

좋아, 원자 왼쪽 이동을 수행하는 기능이 필요합니다. 내 프로세서에는 이에 대한 기본 작업이 없으며 표준 라이브러리에는 이에 대한 기능이 없으므로 내가 직접 작성하는 것처럼 보입니다. 여기에 간다 :

void atomicLeftShift(std::atomic<int>* var, int shiftBy)
{
    do {
        int oldVal = std::atomic_load(var);
        int newVal = oldVal << shiftBy;
    } while(!std::compare_exchange_weak(oldVal, newVal));
}

이제 루프가 두 번 이상 실행될 수있는 두 가지 이유가 있습니다.

  1. 내가 왼쪽 근무를하는 동안 다른 사람이 변수를 변경했습니다. 내 계산 결과는 다른 사람의 쓰기를 효과적으로 지울 수 있기 때문에 원자 변수에 적용해서는 안됩니다.
  2. 내 CPU가 트림되고 약한 CAS가 가짜로 실패했습니다.

솔직히 어느 쪽이든 상관 없어요. 왼쪽 시프트는 실패가 거짓이더라도 다시 할 수있을만큼 충분히 빠릅니다.

무슨 일이있어 빠르고하지만, 강력한 CAS의 요구가 강한하기 위해 주위에 약한 CAS를 포장 할 수있는 여분의 코드입니다. 이 코드는 약한 CAS가 성공할 때 많은 일을하지 않습니다.하지만 실패 할 때 강한 CAS는 케이스 1인지 케이스 2인지 확인하기 위해 몇 가지 탐정 작업을 수행해야합니다. 그 탐정 작업은 두 번째 루프의 형태를 취합니다. 효과적으로 내 루프 내부에서. 두 개의 중첩 루프. 당신의 알고리즘 선생님이 지금 당신에게 눈부신 것을 상상해보십시오.

그리고 앞서 언급했듯이 그 탐정 작업의 결과는 신경 쓰지 않습니다! 어느 쪽이든 저는 CAS를 다시 실행할 것입니다. 따라서 강력한 CAS를 사용하면 정확히 아무것도 얻지 못하며 작지만 측정 가능한 효율성을 잃게됩니다.

즉, 약한 CAS는 원자 업데이트 작업을 구현하는 데 사용됩니다. 강력한 CAS는 CAS의 결과에 신경을 쓸 때 사용됩니다.


0

위의 대부분의 답변은 "가짜 실패"를 일종의 문제, 성능 대 정확성 절충으로 다룬다 고 생각합니다.

대부분의 경우 약한 버전이 더 빠르다는 것을 알 수 있지만 스퓨리어스 실패의 경우 속도가 느려집니다. 그리고 강력한 버전은 가짜 실패 가능성이없는 버전이지만 거의 항상 느립니다.

저에게 가장 큰 차이점은이 두 버전이 ABA 문제를 처리하는 방법입니다.

약한 버전은로드와 저장 사이에 아무도 캐시 라인을 건드리지 않은 경우에만 성공하므로 100 % ABA 문제를 감지합니다.

강력한 버전은 비교가 실패 할 경우에만 실패하므로 추가 조치 없이는 ABA 문제를 감지하지 못합니다.

따라서 이론적으로 약한 아키텍처에서 약한 버전을 사용하는 경우 ABA 감지 메커니즘이 필요하지 않으며 구현이 훨씬 간단하여 성능이 향상됩니다.

그러나 x86 (강력한 아키텍처)에서는 약한 버전과 강력한 버전이 동일하며 둘 다 ABA 문제가 있습니다.

따라서 완전한 크로스 플랫폼 알고리즘을 작성하는 경우 어쨌든 ABA 문제를 해결해야하므로 약한 버전을 사용하여 성능상의 이점은 없지만 가짜 실패를 처리하면 성능이 저하됩니다.

결론적으로, 이식성과 성능상의 이유로 강력한 버전은 항상 더 좋거나 같은 옵션입니다.

약한 버전은 ABA 대응책을 완전히 건너 뛸 수 있거나 알고리즘이 ABA에 관심이없는 경우에만 더 나은 옵션이 될 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.