C # : thread-safe와 atomic의 차이점은 무엇입니까?


대답 1:

스레드 안전 수단은 여러 스레드에서 액세스 할 때 엉망이되지 않습니다. 원자는 그와 같은 맥락에서 불가분의와 동등한 불가분의 것을 의미한다.

잠금을 구현하려면 다음 두 가지 선택 사항이 있습니다.

  1. 테스트 및 설정과 같이 전체적으로 실행되는 특수 복합 명령어 인 원 자성 연산에 대한 하드웨어 지원을 제공하십시오.

세부 사항의 예에서 두 가지 모두 안전하지 않습니다. 내가 올바르게 이해했다면 다음과 같은 것을 의미합니다.

공공 클래스 안전하지 않은
{
    개인 객체 ulock = 새 객체 ();

    public int Unsafe1 {get; 세트; } = 0;

    private int _unsafe2 = 0;
    공개 int 안전하지 않은
    {
        가져 오기
        {
            잠금 (ulock)
            {
                _unsafe2를 반환;
            }
        }

        세트
        {
            잠금 (ulock)
            {
                _unsafe2 = 값;
            }
        }
    }
}

테스트 코드 :

var u = new Unsafe ();

Parallel.For (0, 10000000, _ => {u.Unsafe1 ++;});
Parallel.For (0, 10000000, _ => {u.Unsafe2 ++;});

Console.WriteLine (string.Format ( "{0}-{1}", u.Unsafe1, u.Unsafe2));

결과 (가능한 많은 것 중 하나) :

4648265-4149827

둘 다 업데이트의 절반 이상이 사라졌습니다.

그 이유는 ++가 원자 적이 지 않기 때문입니다. 실제로는 세 가지 별도의 작업입니다.

  1. 값을 얻습니다 .1을 값에 추가하십시오. 값을 설정하십시오.

원자 단위로 증분 작업을 제공하여이 문제를 해결할 수 있습니다. 여러 가지 방법이 있지만 다음 두 가지가 있습니다.

공공 클래스 안전
{
    개인 객체 잠금 = 새 객체 ();

    public int Safe1 {get; 세트; }
    공공 무효 SafeIncrement1 ()
    {
        잠금 (ulock)
        {
            this.Safe1 ++;
        }
    }

    private int _safe2 = 0;
    공공 int Safe2
    {
        가져 오기
        {
            return _safe2;
        }
        세트
        {
            _safe2 = 값;
        }
    }
    공공 무효 SafeIncrement2 ()
    {
        연동. 증가 (ref_safe2);
    }
}

테스트 코드 :

var s = new Safe ();

Parallel.For (0, 10000000, _ => {s.SafeIncrement1 ();});
Parallel.For (0, 10000000, _ => {s.SafeIncrement2 ();});

Console.WriteLine (string.Format ( "{0}-{1}", s.Safe1, s.Safe2));

두 경우 모두 결과가 정확합니다. 첫 번째는 전체 복합 ++ 작업을 잠그고 두 번째는 원자 적 작업에 하드웨어 지원을 사용합니다.

Interlocked.Increment를 사용하는 위의 두 번째 변형은 훨씬 빠르지 만 실제로는 레벨이 낮으며 기본적으로 수행 할 수있는 작업이 제한되어 있습니다. 그러나 Interlocked 패키지의 작업을 사용하여 다음을 구현할 수 있습니다.

  1. 익숙한 잠금 ( "비관적 동시성"이라고 함)은 작업이 중단 될 것이라고 가정하기 때문에 공유 리소스를 얻을 때까지 시작하지 않아도됩니다. 비교 및 스왑에서는 시작시 기록한 특수 "카나리아"값을 사용하고 마지막에 변경되지 않았는지 확인하십시오. 다른 스레드가 발생하면 카나리아가 종료되므로 트랜잭션을 처음부터 다시 시도하는 것이 좋습니다. 이를 위해서는 자체 코드도 원 자성이어야합니다. 중간 결과를 공유 상태로 쓸 수 없으며, 작업을 수행하지 않은 것처럼 완전히 성공하거나 완전히 실패해야합니다.

대답 2:

완전히 다른 두 가지. 스레드 안전은 각 스레드가 다른 스레드의 조작을 망칠 필요없이 (예 : 다른 스레드가 사용중인 변수 인 경우 값 변경) 여러 다른 스레드에서 반복적으로 호출 할 수있는 방식으로 작성된 함수를 의미합니다.

원자는 객체의 인스턴스 하나를 생성하는 것을 의미합니다. (얼마나 자주 참조하든 관계없이 항상 하나의 인스턴스를 볼 수 있습니다)


대답 3:

원자 연산은 내부에서 원자 연산을 사용하는 Mutexes 또는 Semaphores와 같은 일종의 잠금을 사용하거나 원자 및 메모리 펜스를 사용하여 잠금없는 동기화를 구현하여 스레드 안전을 달성하는 방법입니다.

따라서 기본 데이터 유형의 원자 연산은 스레드 안전을 달성하기위한 도구이지만 일반적으로 서로 의존하는 여러 작업이 있으므로 스레드 안전을 자동으로 보장하지 않습니다. Mutexes를 사용하여 이러한 작업을 중단없이 수행해야합니다.

예, c #에서 이러한 원자 데이터 유형 중 하나를 작성하는 것은 스레드로부터 안전하지만 스레드 안전에서 사용하는 기능을 만들지는 않습니다. 두 번째 스레드가 "동시에"액세스하는 경우에도 단일 쓰기가 올바르게 실행되도록합니다. 절대로, 현재 스레드에서 다음에 읽은 읽기는 다른 스레드가 이전에 쓴 값을 얻을 수 없으며 읽은 값만 유효합니다.