현재의 "잠금없는"구현은 대부분 동일한 패턴을 따릅니다.
- * 주를 읽고 사본을 만드십시오 **
- * 사본 수정 **
- 연동 작업을하다
- 실패하면 재시도
(* 선택 사항 : 데이터 구조 / 알고리즘에 따라 다름)
마지막 비트는 스핀 락과 매우 유사합니다. 사실 기본 스핀 락 입니다. :)
저는 이것에 대해 @nobugz에 동의합니다. 잠금없는 멀티 스레딩에서 사용되는 연동 작업의 비용 은 수행해야하는 캐시 및 메모리 일관성 작업에 의해 좌우됩니다 .
그러나 "잠금이없는"데이터 구조로 얻을 수있는 것은 "잠금"이 매우 세밀하다는 것 입니다. 이렇게하면 두 개의 동시 스레드가 동일한 "잠금"(메모리 위치)에 액세스 할 가능성이 줄어 듭니다.
대부분의 경우 트릭은 전용 잠금이 없다는 것입니다. 대신 배열의 모든 요소 또는 연결 목록의 모든 노드를 "스핀 잠금"으로 취급합니다. 마지막으로 읽은 이후 업데이트가 없으면 읽고 수정하고 업데이트를 시도합니다. 있는 경우 다시 시도하십시오.
이렇게하면 추가 메모리 또는 리소스 요구 사항을 도입하지 않고도 "잠금"(오, 죄송합니다, 비 잠금 :)이 매우 세밀 해집니다.
더 세밀하게 만들면 대기 확률이 감소합니다. 추가 리소스 요구 사항을 도입하지 않고 가능한 한 세분화하면 좋을 것 같지 않습니까?
그러나 대부분의 재미는 올바른 적재 / 점포 주문 을 보장하는 데서 올 수 있습니다 .
직관과 달리 CPU는 메모리 읽기 / 쓰기 순서를 자유롭게 변경할 수 있습니다. 그런데 매우 똑똑합니다. 단일 스레드에서이를 관찰하는 데 어려움을 겪을 것입니다. 그러나 다중 코어에서 다중 스레딩을 시작하면 문제가 발생합니다. 당신의 직관은 무너질 것입니다. 단지 명령이 당신의 코드에서 더 일찍 나온다고해서 그것이 실제로 더 일찍 일어날 것이라는 것을 의미하지는 않습니다. CPU는 명령을 순서대로 처리 할 수 있습니다. 특히 메모리 액세스가있는 명령에이 작업을 수행하여 주 메모리 대기 시간을 숨기고 캐시를 더 잘 활용하는 것을 좋아합니다.
이제 코드 시퀀스가 "하향식"으로 흐르지 않고 마치 시퀀스가 전혀없는 것처럼 실행되며 "악마의 놀이터"라고 불릴 수 있다는 것은 직감에 반합니다. 로드 / 스토어 재주문이 발생하는 것에 대해 정확한 답변을 제공하는 것은 불가능하다고 생각합니다. 대신에, 하나는 항상 측면에서 말하는 메이스 와 mights 와 캔 과 최악의 준비. "아, CPU 는 이 읽기를 쓰기 전에 오도록 재정렬 할 수 있으므로 바로 여기,이 지점에 메모리 장벽을 두는 것이 가장 좋습니다."
사항에도 이러한 사실에 의해 복잡 메이스 와 mights는 CPU 아키텍처에 걸쳐 다를 수 있습니다. 그것은 수 , 예를 들면, 그 어떤 경우가 발생하지 보장 한 구조에서 발생할 수있는 또 다른에.
"잠금없는"멀티 스레딩을 제대로하려면 메모리 모델을 이해해야합니다.
그러나 메모리 모델을 얻고 올바른 것을 보장하는 것은 사소한 일이 아닙니다. 이 이야기에서 알 수MFENCE
있듯이 Intel과 AMD는 JVM 개발자들 사이에서 약간의 혼란 을 야기 하는 문서를 수정 했습니다 . 결과적으로 개발자가 처음부터 의존했던 문서는 처음에는 그렇게 정확하지 않았습니다.
.NET의 잠금은 암시 적 메모리 장벽을 생성하므로 안전하게 사용할 수 있습니다 (대부분의 경우 ... 예를 들어 Joe Duffy-Brad Abrams-Vance Morrison의 지연 초기화, 잠금, 휘발성 및 메모리의 위대함 을 참조하십시오. :) (해당 페이지의 링크를 따라 가십시오.)
추가 보너스로, 사이드 퀘스트에서 .NET 메모리 모델을 소개 받게됩니다 . :)
Vance Morrison의 "oldie but goldie"도 있습니다 : 모든 개발자가 멀티 스레드 앱에 대해 알아야 할 사항 .
... 물론 @Eric이 언급했듯이 Joe Duffy 는 주제에 대한 확실한 읽기입니다.
좋은 STM은 세분화 된 잠금에 가까워 질 수 있으며 아마도 손으로 만든 구현에 가깝거나 동등한 성능을 제공 할 것입니다. 그중 하나는 MS DevLabs 프로젝트 의 STM.NET 입니다 .
.NET 전용 열광자가 아니라면 Doug Lea가 JSR-166에서 훌륭한 작업을 수행했습니다 .
Cliff Click 은 Java 및 .NET 동시 해시 테이블과 마찬가지로 잠금 스트라이핑에 의존하지 않는 해시 테이블을 흥미롭게 가지고 있으며 750 CPU로 잘 확장되는 것으로 보입니다.
Linux 영역에 도전하는 것을 두려워하지 않는다면, 다음 기사는 현재 메모리 아키텍처의 내부와 캐시 라인 공유가 성능을 어떻게 파괴 할 수 있는지에 대한 더 많은 통찰력을 제공합니다. 모든 프로그래머가 메모리에 대해 알아야하는 것 .
@Ben은 MPI에 대해 많은 의견을 남겼습니다. 저는 MPI가 일부 영역에서 빛을 발할 수 있다는 데 진심으로 동의합니다. MPI 기반 솔루션은 현명하게 시도하는 반 베이크 된 잠금 구현보다 추론하기 쉽고 구현하기 쉬우 며 오류 발생 가능성이 적습니다. (그러나-주관적으로-STM 기반 솔루션의 경우에도 마찬가지입니다.) 또한 많은 성공적인 예제에서 알 수 있듯이 예를 들어 Erlang에서 적절한 분산 응용 프로그램 을 올바르게 작성하는 것이 광년이 더 쉬울 것이라고 확신합니다 .
그러나 MPI는 단일 멀티 코어 시스템 에서 실행될 때 자체 비용과 문제가 있습니다 . 예를 들어 Erlang에서는 프로세스 스케줄링과 메시지 큐 의 동기화와 관련하여 해결해야 할 문제가 있습니다 .
또한 MPI 시스템의 핵심은 일반적으로 "경량 프로세스"를위한 일종의 협력적인 N : M 스케줄링 을 구현합니다 . 예를 들어 이는 경량 프로세스간에 불가피한 컨텍스트 전환이 있음을 의미합니다. 이것이 "고전적인 컨텍스트 전환"이 아니라 대부분 사용자 공간 작업이고 빠르게 만들 수 있다는 것은 사실이지만 연동 작업이 걸리는 20-200 사이클 아래로 가져올 수 있는지 진심으로 의심합니다 . 사용자 모드 컨텍스트 전환은 확실히 느립니다.Intel McRT 라이브러리에서도. 경량 프로세스를 사용한 N : M 스케줄링은 새로운 것이 아닙니다. LWP는 오랫동안 솔라리스에있었습니다. 그들은 버려졌습니다. NT에는 섬유가있었습니다. 지금은 대부분 유물입니다. NetBSD에는 "활성화"가있었습니다. 그들은 버려졌습니다. 리눅스는 N : M 스레딩이라는 주제를 가지고있었습니다. 지금 쯤이면 다소 죽은 것 같습니다.
때때로 새로운 경쟁자가 있습니다. 예를 들어 Intel의 McRT 또는 Microsoft의 ConCRT 와 함께 가장 최근의 사용자 모드 스케줄링 .
가장 낮은 수준에서는 N : M MPI 스케줄러가 수행하는 작업을 수행합니다. Erlang 또는 모든 MPI 시스템은 새로운 UMS 를 활용하여 SMP 시스템에서 큰 이점을 얻을 수 있습니다 .
나는 OP의 질문이 어떤 솔루션에 대한 / 반대의 주관적인 주장의 장점에 관한 것이 아니라고 생각하지만, 대답해야한다면 작업에 달려 있다고 생각합니다. 단일 시스템 으로 많은 코어 , 중 낮은 잠금 / "잠금없는"기술 또는 STM 위의 주름이 밖으로 다림질 경우에도, 성능면에서 최상의 결과를 얻을 것입니다 아마 성능 현명한 MPI를 솔루션 언제든지 이길 것 예 : Erlang에서.
단일 시스템에서 실행되는 약간 더 복잡한 것을 빌드하려면 고전적인 거친 잠금을 선택하거나 성능이 큰 관심사 인 경우 STM을 선택합니다.
분산 시스템을 구축하기 위해 MPI 시스템은 아마도 자연스러운 선택을 할 것입니다.
가하는 것으로 MPI 구현 을위한 .NET뿐만 아니라 (그들은으로 활성화되지 것 같다하지만).