ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 메모리 락과 락없는 프로그래밍
    @ 17. 1 ~ 18/C# 멀티스레드 2018. 7. 3. 00:02

    임계영역같은 스타일의 동기화가 아닌 형태로 수행할 수 있는 스레드 간의 읽기, 쓰기를 락 없는 프로그래밍이라고 불린다.

    (특히 읽기와 쓰기에 대해 만들어지는 원자성과 순서 보장을 잘 알아야한다)


    제대로 구현했다면 임계영역은 다른 스레드에서 동시적으로 실행하는 영역의 원자성과 직렬성을 보장한다.

    이런보장은 근본적인 정확성의 속성이다.


    메모리 동작은 규칙적으로 프로그램을 실행할 책임이 있는 소프트웨어와 하드웨어에 의해 재배치 된다.


    1. 컴파일러는 소스를 컴파일된 프로그램 명령어로 변환하는 과정에서 종종 최적화를 수행한다.

    (읽기와 쓰기가 옮겨지거나 제거 또는 더해진다)


    2. 프로세서는 명령어 파이프라인을 통해서 다른위치인 a와 b인 메모리 읽기쌍을 동시에 실행한다.

    예를들어 문자 그대로 a가 원래 소스코드의 b에 앞선다고 하더라도 b는 a전에 완료되는 것이 허용된다.

    이 동작은 프로세서가 해가 없다고 판단하면 합법적일수 있다. 즉, 둘사이에 종속성이 없다.


    3. 캐시 일관성이라 불리는 하드웨어 장치를 통해 전역적으로 일관성이 유지돼야만 한다.

    이는 쓰기가 실질적으로 주 메모리에 도달할때와 읽기가 지역 프로세서 캐시를 새롭게 해야할 때 정확히 다룬다는 것을 말한다.

    (많은 프로세서는 저장을 지연하는 쓰기버퍼를 사용한다.)

    이런 요소들은 읽기와 쓰기가 순서에 상관없이 실행되는 것처럼 할 수 있다.


    위의 세가지 관점은 일반적으로 명령어 재배치라는 용어로 표현된다.



    실행되는 것이 항상 ㅎ작성한 것과 동일한 건 아니다.

    t0            t1

    x=1;        y=1;

    a=y;        b=x;

    스레드 t0와 t1이 둘다 한번에 실행되고나서 a==b==0일 가능성은?

    명령어 순서 재배치로 인해서 가능하다.

    프로그램은 정적(컴파일러) 동적(프로세서나 메모리 시스템에 의해) 네 개의 명령어의 순서가 어떤순서로도 변경이 가능해진다.


    t0            t1

    a=y;        b=x;

    x=1;        y=1;

    이렇게도 가능하다는 것이다.


    이런 종류의 오류는 찾기가 힘들다. 

    이런 순서재배치는 저장버퍼의 사용으로 인해 발생할 것이다. 물론 컴파일러도 한몫한다.


    모든것이 컴파일러와 프로세서에 매우 의존적이라고 할지라도 다음 세가지는 저수준의 락 프로그래밍을 프로그래머의 영역으로

    갖고 올 수 있는 방법이다.

    1. 어떤 경우라도 코드의 순차적인 검증을 깨지는 않는다. 단, 스레드간의 통신에 사용되는 읽기와 쓰기는 문제가 있다.

    2. 관련된 데이터의 종속성은 순서가 재배채되는것을 제한한다. 

    3. 모든 플랫폼은 메모리 일관성 모델이나 짧게 줄여 메모리 모델을 제공한다.




    데이터 종속과 재배치의 영향

    컴파일러와 프로세서는 동작을 옮길때 동작 사이의 데이터 의존성을 준수하는데 신중하다.

    그렇게 하지 않으면 정확하게 짜인 알고리즘을 순차적으로 실행할때조차도 부정확하게 나타나기 때문이다.

    이런측면에서 데이터 의존성은 하나의 프로세서나 스레드에서 실행되는 연속된 명령어에서의 동작에만 적용된다.

    다시말하면 다른 프로세서에서 실행하는 코드간의 의존성은 고려되지 않는다.


    세 가지 종류의 데이터 의존성이 있다.

    1. 진정한 의존성인 읽기 후 쓰기 의존성

    메모리의 어떤 장소가 저장되고 난 후에 읽혀질때 발생한다. 읽기는 쓰기나 프로그램이 이전 값(없어진 데이터)을 보기전에는 이동할 수 없다.

    X = 1;    //S0

    Y = X;  //S1, X를 읽기 위해서는 쓰기 즉 위에 S0을 보기전에는 이동할 수 없단것이다.


    이 코드에서 X에 쓰기는 S0에서 발생하고 X의 읽기가 S1에서 발생한다.

    명령어의 순서가 뒤바뀌면 결과는 틀리게 될 것이다.


    2. 출력 의존성, 즉 쓰기 후 쓰기

    동일한 변수가 여러번 써질 때 발생한다. 

    X = 0; //S0

    X = 1; //S1


    여기서 S0와 S1를 변경했다면 실행 후에 X값은 1대신 0의 값을 가질 것이다.

    이 값은 틀렸고 이런 재배치는 허용하면 안된다.

    컴파일러는 종종 이런 쓰기를 하나로 합치고 첫 번째 것을 지우지만, 마지막 값을 유지하고 이는 재배치하는 것과는 다르다.


    3. 반의존성, 쓰기 후 읽기

    어떤 값이 읽혀지고 난 후에 써진다면 프로그램 작성자는 읽기에서 아마도 쓰기가 발생하기 전의 변수 값을 보기를 기대할 것이다.

    Y = X;    //S0

    X = 1;    //S1

    원래의 X값이 0을 가진다고 가정할때 S0의 읽기전에 S1의 쓰기로 이동하는 것은 Y값을 0이 아닌 1과 같게 해 잘못된 값을 발생시킨다.

    그렇기 때문에 위에 반의존성은 재배치가 되지않는다.


    데이터 의존성은 이행성을 가진다.

    이행성이란 아래 예를 보면

    X = 1; //S0

    Y = X; //S1

    Z = Y; //S2

    이 예에서 S2는 S1에 대해 1번 진정한 의존성을 갖고 S1은 S0에 대해 진정한 의존성을 가진다 결국 의존성은 이행성을 갖기 때문에

    S2는 또한 S0에 대해 진정한 의존성을 가진다.


    '@ 17. 1 ~ 18 > C# 멀티스레드' 카테고리의 다른 글

    스레드 풀 스레드 관리  (0) 2018.06.30
    읽기 / 쓰기 락  (0) 2018.06.27
    스레드 로컬 저장소  (0) 2018.06.21
    스레드 동기화란?  (0) 2018.03.05
    동시성 컬렉션 사용  (0) 2017.08.20
Designed by Tistory.