Story 19. 골치 아픈 멀티스레드 버그
           : 하이젠버그
         chois79
하이젠버그

● 이 버그는 문제가 있음이 확실하지만 재현이 어려움
   ○ ex) 오류 탐색을 위한 코드로 인해 재현이 안되는 경우

● 용어의 유래
   ○ 불확정성의 원리: 베르너 하이젠 베르크
   ○ 하이젠버그 = 하이젠 + 버그 
멀티스레드 버그-데이터 레이스

● 데이터 레이스
   ○ 공유 데이터가 두 스레드 이상에서 동시에 접근 
   ○ 단, 하나의 접근이 쓰기인 경우
  int global_data = 0;

  DWORD CALLBACK ThreadFunc(void* thread_id) {
    for(int i = 0; i < 10000000; i++)
       ++global_data;
    printf("result = %dn", global_data);
  }

 ○ 2개의 스레드에 의해 위 함수가 실행될 때 그 결과를 예
   측할 수 없음
멀티스레드 버그 - 원자성 위반

● 일련의 작업이 침범 받지 않고 안전하게 수행되지 못함 
   ○ 데이터 레이스가 아니지만 오류 발생

                  모질라 nxXULDocument.cpp에서 발견
              Thread_1                          Thread_2

              void LoadScript(nsSpt * aspt) {
                Lock(1);
                gCurrentScript = aspt;
                LaunchLoad(aspt);
                Unlock(1);
              }                                 Lock(1);
                                                gCurrentScript = NULL;
              // LaunchLoad의 결과로                Unlock(1);
              // 비동기적으로 Callback
              void OnLoadComplete() {
                 Lock(1);
                 gCurrentScript->compile();
                 Unlock(1);
              }
멀티스레드 버그 - 순서 위반

● 의도한 순서가 멀티 스레드 상황에서 지켜지지 않을 경우 




       모질라의 순서 위반 버그
   Thread_1                   Thread_2

   int ReadWriteProc(...) {   void DoneWating(..) {
      ...                       ...
      PBReadAsync(&P);          io_pending = TRUE;
      io_pending = TRUE;        ...
                              }

       ...
       while(io_pending) {
       ...
       }
   }
해결 방안

● 데이터 레이스
   ○ 락으로 수정 가능
● 그외의 멀티 스레드 버그
   ○ 락의 순서 조정
   ○ 현재의 락만으로는 모든 병행성 문제를 해결할 수 없음
   ○ 알고리즘의 수정
      ■ 자료 구조 및 알고리즘을 병행성 문제가 없도록 수정
결론

● 멀티 스레드 버그는 재현이 힘들어서 디버깅이 힘들다.

● 락으로 데이터 레이스 문제를 해결할 수 있지만, 멀티스레드
  문제 전체를 해결 할 수는 없다.
Story 20. 어두운 병렬 프로그래밍
       그리고 그 미래는?
        chois79
병렬 프로그래밍의 문제: 가짜 공유 

● 공유되지 않는 데이터 인데 캐시 구조의 특성으로 공유되는
  것으로 인식 => 성능 저하 현상 발생
 volatile int data1;
 volatile int data2;

 DWORD CALLBACK TestThread1( ... ) {
   // 0번 CPU에서 작동
   SetThreadAffinityMask(GetCurrentThread(), 1 << 0);
   for(int i = 0; i < 150000000; ++i)
      data1 = data1 +1;
 }

 DWORD CALLBACK TestThread2( ... ) {
   // 2번 CPU에서 작동
   SetThreadAffinityMask(GetCurrentThread(), 1 << 0);
   for(int i = 0; i < 150000000; ++i)
      data2 = data2 +1;
 }
가짜공유문제 - Cont'd

● 0번 코어가 Data1을 1번 코어가  Data2를 사용
● 양쪽 코어의 같은 캐쉬 라인에 위치하게 됨
   ○ 캐시는 일반적으로 4바이트 이상이 한 단위로 처리
   ○ 두 변수의 주소가 인접
● 코어가 한 캐시라인을 업데이트 하면 다른 코어가 가진 사본
  을 모두 무효화
   ○ 즉, 가짜 공유 문제 발생
   ○ 양쪽 모두 계속해서 캐시 미스 발생

         코어 0         코어 1
   data1              data2
     1     2      1     2
가짜 공유 문제 - Cont'd

● 해결책
   ○ 캐쉬 선언 만큼 데이터 선언을 분리
 #define CACHE_LINE 64
 #define CACHE_ALIGN_MSVC __declspec(align(CACHE_LINE))
 #define CACHE_ALIGN_GCC __attribute__((aligned(CACHE_LINE)))

 volatile CACHE_ALIGN_MSVC int data1;
 volatile CACHE_ALIGN_MSVC int data2;

 volatile struct {
   int data1;
   char padding[CACHE_LINE];
   int data2;
 } DATA;
미래의 병렬 프로그래밍 방법론

● 병행성 해결을 위한 하드웨어적 방법
   ○ 트랜잭셔널 메모리(TM)
      ■ 원자적으로 실행될 부분을 키워드로 선언
    void TransferValue(FIFO Q1, FIFO Q2) {
      __atomic { // 트랜잭션 영역
         v = Q1.dequeue();
         Q2.enqueuue(v);
      }
    }


   ■ 충돌이 감지되면, 트랜잭션을 무효화 하고, 다시 실행
   ■ TM 블록 내에 I/O가 있을 경우 재실행이 힘듬
   ■ ACID중 원자성과 독립성만 보장
   ■ 10년 내에 실용화 가능성이 있음
미래의 병렬 프로그래밍 방법론-
Cont'd
● 병행성 해결을 위한 하드웨어적 방법
   ○ 스레드 수준 투기(TLS)
      ■ 분기 예측과 메모리 실행을 스레드 수준까지 올림
      ■ 어떤 코드를 먼저 여러 스레드에서 실행 후, 충돌이
        없다면 병렬로 실행
      ■ 컴파일러가 적절하게 TLS를 활용한 코드를 생성
      ■ 이론상으로만 존재
결론

● 가짜 공유 문제는 멀티 코어 구조로 인한 것이므로 프로그래
  머의 주의가 필요

● 앞으로의 병렬 프로그래밍 방법
   ○ 하드웨어적 방법
      ■ 트랜잭션 메모리
      ■ 스레드 수준 투기

MutiCore 19-20

  • 1.
    Story 19. 골치아픈 멀티스레드 버그 : 하이젠버그 chois79
  • 2.
    하이젠버그 ● 이 버그는문제가 있음이 확실하지만 재현이 어려움 ○ ex) 오류 탐색을 위한 코드로 인해 재현이 안되는 경우 ● 용어의 유래 ○ 불확정성의 원리: 베르너 하이젠 베르크 ○ 하이젠버그 = 하이젠 + 버그 
  • 3.
    멀티스레드 버그-데이터 레이스 ●데이터 레이스 ○ 공유 데이터가 두 스레드 이상에서 동시에 접근  ○ 단, 하나의 접근이 쓰기인 경우 int global_data = 0; DWORD CALLBACK ThreadFunc(void* thread_id) { for(int i = 0; i < 10000000; i++) ++global_data; printf("result = %dn", global_data); } ○ 2개의 스레드에 의해 위 함수가 실행될 때 그 결과를 예 측할 수 없음
  • 4.
    멀티스레드 버그 -원자성 위반 ● 일련의 작업이 침범 받지 않고 안전하게 수행되지 못함  ○ 데이터 레이스가 아니지만 오류 발생 모질라 nxXULDocument.cpp에서 발견 Thread_1 Thread_2 void LoadScript(nsSpt * aspt) { Lock(1); gCurrentScript = aspt; LaunchLoad(aspt); Unlock(1); } Lock(1); gCurrentScript = NULL; // LaunchLoad의 결과로 Unlock(1); // 비동기적으로 Callback void OnLoadComplete() { Lock(1); gCurrentScript->compile(); Unlock(1); }
  • 5.
    멀티스레드 버그 -순서 위반 ● 의도한 순서가 멀티 스레드 상황에서 지켜지지 않을 경우  모질라의 순서 위반 버그 Thread_1 Thread_2 int ReadWriteProc(...) { void DoneWating(..) { ... ... PBReadAsync(&P); io_pending = TRUE; io_pending = TRUE; ... } ... while(io_pending) { ... } }
  • 6.
    해결 방안 ● 데이터레이스 ○ 락으로 수정 가능 ● 그외의 멀티 스레드 버그 ○ 락의 순서 조정 ○ 현재의 락만으로는 모든 병행성 문제를 해결할 수 없음 ○ 알고리즘의 수정 ■ 자료 구조 및 알고리즘을 병행성 문제가 없도록 수정
  • 7.
    결론 ● 멀티 스레드버그는 재현이 힘들어서 디버깅이 힘들다. ● 락으로 데이터 레이스 문제를 해결할 수 있지만, 멀티스레드 문제 전체를 해결 할 수는 없다.
  • 8.
    Story 20. 어두운병렬 프로그래밍 그리고 그 미래는? chois79
  • 9.
    병렬 프로그래밍의 문제:가짜 공유  ● 공유되지 않는 데이터 인데 캐시 구조의 특성으로 공유되는 것으로 인식 => 성능 저하 현상 발생 volatile int data1; volatile int data2; DWORD CALLBACK TestThread1( ... ) { // 0번 CPU에서 작동 SetThreadAffinityMask(GetCurrentThread(), 1 << 0); for(int i = 0; i < 150000000; ++i) data1 = data1 +1; } DWORD CALLBACK TestThread2( ... ) { // 2번 CPU에서 작동 SetThreadAffinityMask(GetCurrentThread(), 1 << 0); for(int i = 0; i < 150000000; ++i) data2 = data2 +1; }
  • 10.
    가짜공유문제 - Cont'd ●0번 코어가 Data1을 1번 코어가  Data2를 사용 ● 양쪽 코어의 같은 캐쉬 라인에 위치하게 됨 ○ 캐시는 일반적으로 4바이트 이상이 한 단위로 처리 ○ 두 변수의 주소가 인접 ● 코어가 한 캐시라인을 업데이트 하면 다른 코어가 가진 사본 을 모두 무효화 ○ 즉, 가짜 공유 문제 발생 ○ 양쪽 모두 계속해서 캐시 미스 발생 코어 0 코어 1 data1 data2 1 2 1 2
  • 11.
    가짜 공유 문제- Cont'd ● 해결책 ○ 캐쉬 선언 만큼 데이터 선언을 분리 #define CACHE_LINE 64 #define CACHE_ALIGN_MSVC __declspec(align(CACHE_LINE)) #define CACHE_ALIGN_GCC __attribute__((aligned(CACHE_LINE))) volatile CACHE_ALIGN_MSVC int data1; volatile CACHE_ALIGN_MSVC int data2; volatile struct { int data1; char padding[CACHE_LINE]; int data2; } DATA;
  • 12.
    미래의 병렬 프로그래밍방법론 ● 병행성 해결을 위한 하드웨어적 방법 ○ 트랜잭셔널 메모리(TM) ■ 원자적으로 실행될 부분을 키워드로 선언 void TransferValue(FIFO Q1, FIFO Q2) { __atomic { // 트랜잭션 영역 v = Q1.dequeue(); Q2.enqueuue(v); } } ■ 충돌이 감지되면, 트랜잭션을 무효화 하고, 다시 실행 ■ TM 블록 내에 I/O가 있을 경우 재실행이 힘듬 ■ ACID중 원자성과 독립성만 보장 ■ 10년 내에 실용화 가능성이 있음
  • 13.
    미래의 병렬 프로그래밍방법론- Cont'd ● 병행성 해결을 위한 하드웨어적 방법 ○ 스레드 수준 투기(TLS) ■ 분기 예측과 메모리 실행을 스레드 수준까지 올림 ■ 어떤 코드를 먼저 여러 스레드에서 실행 후, 충돌이 없다면 병렬로 실행 ■ 컴파일러가 적절하게 TLS를 활용한 코드를 생성 ■ 이론상으로만 존재
  • 14.
    결론 ● 가짜 공유문제는 멀티 코어 구조로 인한 것이므로 프로그래 머의 주의가 필요 ● 앞으로의 병렬 프로그래밍 방법 ○ 하드웨어적 방법 ■ 트랜잭션 메모리 ■ 스레드 수준 투기