Skip to content

Chapter 10, Testing and debugging multithreaded applications

YounJae Lee edited this page Mar 1, 2016 · 2 revisions

Chapter 10, Testing and debugging multithreaded applications

이번 챕터에서는

  • 동시성 관련 버그
  • 테스팅 및 코드리뷰를 통한 버그 찾기
  • 멀티쓰레드 테스트 디자인
  • 멀티쓰레드 코드 성능 테스트

지금까지는 동시성 코드를 작성하기 위한 도구들과, 그것들을 어떻게 사용하는지, 그리고 전체적인 디자인과 코드 구조를 알아보았다. 하지만 아직 소프트웨어 개발에서 중요한 부분인 테스팅과 디버깅에 관해서 이야기하지 않았다. 만약 당신이 쉬운 동시성 프로그램 테스트 방법을 바라고 이 챕터를 읽는다면 매우 실망할 것이다. 동시성코드의 테스팅과 디버깅은 어렵다. 나는 이것들을 쉽게 만드는 테크닉과 함께 중요하다고 생각되는 것들을 주겠다.

테스팅과 디버깅은 동전의 양면과 같다 - subject 혹시 있을지 모르는 버그를 찾기 위해 너의 코드를 테스트하고, 버그를 없애려고 디버깅을 한다. 사용자가 니 애플리케이션 버그를 찾기보다 너의 테스트에서 찾은 버그를 없애는 편이 낫다.

우리가 테스팅이나 디버깅을 살펴보기 전 발생할 수 있는 문제를 이해하는 것이 중요하니 같이 살펴보자.

10.1 동시성 관련 버그의 유형

넌 동시성 코드에서 모든 종류의 버그를 볼 수 있고 그것은 특별하지 않다. 하지만 어떤 타입의 버그들은 동시성코드의 이용과 직접적으로 관련되어 있으며 고로 특히 이 책과 관련이 있다. 일반적으로 이런 동시성 관련된 버그는 크게 두가지로 나뉜다:

  • Unwanted blocking
  • Race conditions

이것들은 거대한 범주이니 작게 나눠보자. 먼저 Unwanted blocking을 살펴본다.

Unwanted blocking

Unwanted blocking 이란 무엇일까? 먼저 쓰레드가 무언가를 기다리고 있어 진행할 수 없게 되었을때 block 되었다. 이것은 일반적으로 mutex, condition variable, future 같은 것이나 이것은 I/O를 기다릴 것이다. 이것은 멀티쓰레드 코드의 자연스러운 부분이나 항상 바람직한 것은 아니다-그런 이유로 unwanted blocking 문제. 이것은 우리를 다음 질문으로 이끈다: 왜 이 블럭킹은 원치않았나? 일반적으로 다른 쓰레드 또한 어떤 일을 수행하는 블락된 쓰레레드를 기다리고, 차례로 그 쓰레드가 블럭된다. 이것들은 이 주제에 대한 몇가지 변화:

Deadlock

챕터3에서 보았드시, 데드락에 걸린 쓰레드의 경우, 한개의 쓰레드가 다른 쓰레드를 기다리고, 그것이 처음 것을 다시 기다린다. 만약 너의 쓰레드가 데드락이면, 태스크들은 원치않게 종료될 것으로 추정된다. 가장 눈에 띄는 경우, ui에 응답하는 하나의 쓰레드며, 이 경우 응답은 종료될 것이다. 다른 경우로는, 인터페이스 응답이 남아있으나, 어떤 필요한 태스크가 완료되지 않을것이며, 예를들어 찾기가 리턴되지않거나 문서가 프린트되지 않는.

Livelock

라이브락은 데드락에서 한 쓰레드가 다른 쓰레드를 서로 기다리는 것이 비슷하다. 다른점은 블락된 wait 이 블락킹 wait이 아니고 살아있는 체킹 loop인 spink lock과 같은. 심각한 경우, 데드락과 같은 증상을 보인다 (app가 어떤 진행도 보이지 않음), 쓰레드들이 여전히 돌고있고 서로 블락되지 않아 CPU 점유율이 높다는 것만 제외하면. 심각하지 않은 경우, 라이브락은 결국 스케쥴링으로 해결될 것이다, 하지만 이것은 태스크가 라이브락이 되고 긴 지연이었을 것이다 높은 CPU 점유율과 함께.

I/O나 외부 입력을 기다림

만약 너의 쓰레드가 블락되어 외부 입력을 기다리고 처리되지 않았다고 하자, 비록 기다리는 입력이 절대 오지 않더라도. 이것은 그러므로 쓰레드에 의한 원하지 않는 외부입력을 블락하면 다른 쓰레드도 기다리게 될것이다.

원하지 않는 블럭킹을 간단히 설명했다. 레이스 컨디션은 어떨까?

Race conditions

많은 레이스컨디션은 문제는 멀티쓰레드 코드로 인해 발생한다- 많은 데드락과 라이브락이 레이스컨디션 때문에 나타난다. 모든 레이스컨디션이 문제가 있는것은 아니다- 레이스컨디션은 언제든 발생한다 분리된 쓰레드들의 스케쥴링에 따라. 많은 수의 레이스컨디션들은 완전히 온순하다; 예를들어, 어떤 작업 쓰레드가 다음 태스크 속의 태스크 큐를 처리하는 것은 크게 관련이 없다. 그런데 많은 동시성 버그들은 레이스 컨디션에 기인한다. 특히 레이스 컨디션은 자주 다음과 같은 유형의 문제를 야기한다:

Data races

데이터 레이스는 공유 메모리에 동기화되지않은 접근 때문에 발생하는 정의되지않은 행동의 결과로 나타나는 레이스 컨디션의 명확한 유형이다. 데이터레이스에 관해서는 챕터5에서 본 c++ 메모리 모델에서 소개하였다. 데이터레이스는 보통 올바르지 않은 atomic operation의 사용을 통한 쓰레드 동기화를 하거나 적절한 뮤텍스 락 없이 공유 데이터에 접근하는 것으로 발생한다.

Broken invariants

이것들은 댕글링포인터와 같은 것으로 나타낼 수 있다 (다른 쓰레드가 지운 데이터에 엑세스되고 있으니까), 무작위 메모리 오염 (부분 업데이트로 쓰레드가 다른 값을 읽기 때문에), 그리고 더블 프리 (두 쓰레드가 같은 값을 큐에서 pop 하고 연관된 데이터를 삭제할때 같은). The invariants being broken can be temporal- as well as value-based. 만약 분리된 쓰레드의 명령들이 특정 실행 순서가 필요하면 잘못된 동기화가 레이스컨디션을 이끌수 있다 필요된 순서가 가끔 증발된.

Lifetime issues

당신이 불편깨짐에서 발생한 문제들을 묶을수 있었지만, 실제로 이것은 분리된 범주이다. 이 범주에서의 기본적인 문제는 쓰레드가 접근 데이터보다 오래 살면서 접근하는 것이다, 이것은 삭제되거나 파괴된 데이터에 접근하며, 잠재적으로 저장소가 다른 오브젝트에 의해 재사용 될수도 있다. 너는 보통 쓰레드 펑션이 완료되기 전에 스코프를 빠져나간 쓰레드가 참조하는 로컬 변수에서 라이프타임 이슈를 만난다, 하지만 저 시나리오에 한정된 것은 아니다. 쓰레드의 라이프타임과 작동하는 데이터는 어떤 점에서 함께 묶이지 않는다, 이것들은 잠재적으로 쓰레드가 끝나기전 데이터가 파괴되고 쓰레드 펑션은 발 아래에서 양탄자를 꺼냈다?? 만약 니가 쓰레드가 끝나길 기다리려고 직접 join 을 호출했다면, join이 익셉션이 발생해도 skip 되지 않도록 보장하는 것이 필요하다. 이것은 쓰레드에 적용되는 기본적인 익셉션 세이프티 이다.

이것은 문제있는 레이스 컨디션이다.

TODO....

Techniques for locating concurrency-related bugs

동시성 관련 버그를 찾기 위한 기술

가장 명백하고 쉬운 방법은 “look at the code"

Reviewing code to locate potential bugs

잠재적 버그를 찾기 위한 코드 리뷰

  • 대부분 동시성 버그는 미묘한 타이밍 이슈에 실제로 나타난다
  • 동료와 코드리뷰를 해라
  • 안되면 스스로 해라
  • 코드를 자세하게 설명해라
  • 나 자신과 질답을 해봐라

Questions to think about when reviewing multithreaded code

멀티쓰레드 코드를 리뷰할때의 질문에 대해 생각해보자

  • 동시 접근으로부터 어떤 데이터가 보호되어야 하나?
  • 어떻게 데이터가 보호되는 것을 보장할 것인가?
  • 쓰레드들간 코드의 어디에서 이시간이 돌고 있을까????
  • 어떤 뮤텍스들을 이 쓰레드가 잡나?
  • 어떤 뮤텍스들을 다른 쓰레드가 잡을것인가?
  • 쓰레드들간 작업완료 사이의 순서? 이런 요구사항은 어떻게 적용?
  • 이 쓰레드로 부터 로드된 데이터는 여전히 유효한가? 다른 쓰레드들로부터 수정되었나? 6장 이야기를 하네
  • 다른 쓰레드들로부터 데이터가 수정될수 있다고 가정하면 그것의 의미와 어떻게 발생하지 않게 보장할 것인가?

스스로 버그가 없다고 납득하기 위해서 모든 코너 케이스와 가능한 순서를 고려해야 한다

Locating concurrency-related bugs by testing

테스팅으로부터 동시성 버그 찾기

  • 단일쓰레드로 낮추면서 원인을 찾기
  • 싱글코어 이슈와 멀티코어 이슈 가능성 확인(멀티코어는 레이스컨디션과 동기화 혹은 메모리오더링 이슈가 있을수 있다)
테스트 환경에 대한 변수 고려
  • 테스팅 쓰레드 개수
  • 테스팅 머신의 프로세싱 코어와 각 쓰레드를 어떤 코어에서 스케쥴링
  • 어떤 프로세서 아키텍쳐 상에서 실행하나
  • 내 테스트 코드의 적절한 스케쥴링을 어떻게 보장

Designing for testability

테스트용이성을 위한 설계

멀티쓰레드 코드의 테스트는 어렵기에 테스트에 용이하도록 코드를 디자인 하자

코드를 테스트하기 쉽게 작성하기
  • 각 함수와 클래스의 책임은 분명하게
  • 함수는 짧고 간단 명료하게
  • 테스트 코드를 둘러싼 환경을 완전히 콘트롤 할 수 있어야 한다
  • 특정 작업을 수행하는 코드는 시스템 전체에 퍼뜨려놓는것 보다 모아 놓는것이 낫다
  • 코드 작성 전 테스트하는 방법에 대해 생각하기

Best way는 동시성코드의 동시성 제거 대안: 코드를 여러 블락으로 나눈다. read_share, transform, update_shared

Multithreaded testing techniques

멀티쓰레드 테스팅 테크닉

Brute-force testing
  • 많은 스트레스. 많은 횟수 실행, 많은 쓰레드 개수
  • 버그가 나타나기 쉬운 환경 조성.
  • 각 테스트 코드에 양에 따라 결과가 결정.
  • 싱글 프로세서 시스템에서 돌리면 자동직렬화, 멀티코어에서 발생하는 문제 증발시키는 것이 가능
  • 크로스플랫폼이라면 프로세서아키텍쳐에따른 결과 다름
콤비네이션 시뮬레이션 테스팅
  • 에뮬레이팅 시스템에 부분 시스템을 넣어서 테스팅
노출된 문제 를 스페셜라이브러리와 함께 테스팅으로 찾기
  • 라이브러리에 공유데이터 접근이나 락 접근 등을 기록하도록 하여 디버깅 하라는것같다

std에 있는 것을 써서 락을 잡을때 notify해서 알려주게 할수도 있다

멀티쓰레드 테스트 코드 설계

  • 일반 셋업코드는 뭘 하기전에 반드시 수행
  • 특정 쓰레드 셋업 코드는 반드시 각 쓰레드에서 실행
  • 동시에 돌리고자 하는 실제 코드
  • 동시실행이 끝나면 상태를 포함할것

TODO...

Clone this wiki locally