교착 상태를 디버깅 할 때 무엇을 찾으십니까?


25

최근에는 스레딩을 많이 사용하는 프로젝트를 연구하고 있습니다. 나는 그것들을 디자인하는 것이 좋다고 생각한다. 가능한 한 상태 비 저장 디자인을 사용하고 둘 이상의 스레드가 필요로하는 모든 리소스에 대한 액세스를 잠급니다.

그러나 다른 사람들의 스레드 코드를 읽을 때 혼란스러워합니다. 현재 교착 상태를 디버깅하고 있으며 코딩 스타일과 디자인이 개인 스타일과 다르기 때문에 잠재적 교착 상태를 보는 데 어려움을 겪고 있습니다.

교착 상태를 디버깅 할 때 무엇을 찾으십니까?


내 문제에 대한 구체적인 대답이 아닌 교착 상태 디버깅에 대한보다 일반적인 포인터를 원하기 때문에 SO 대신 여기에 묻습니다.
Michael K

실제로 who's 대기 --A-잠금 개최별로 누구를 위해 (참조의 교착 상태 그래프 검사 (여러 다른 사람들이 지적으로) 내가 생각할 수있는 전략은 로그인하는 stackoverflow.com/questions/3483094/...을 일부를 포인터) 및 주석 잠금 ( clang.llvm.org/docs/ThreadSafetyAnalysis.html 참조 ) 코드가 아닌 경우에도 작성자가 주석을 추가하도록 설득하려고 시도 할 수 있습니다. 프로세스에서 버그를 찾아 수정 (아마도 포함) 할 수 있습니다.
돈 해치

답변:


23

상황이 실제 교착 상태 인 경우 (예 : 두 개의 스레드가 두 개의 다른 잠금을 보유하지만 하나 이상의 스레드가 다른 스레드가 보유한 잠금을 원하는 경우) 먼저 스레드의 잠금 순서에 대한 모든 사전 개념을 포기해야합니다. 아무것도 가정하지 마십시오. 보고있는 코드에서 모든 주석을 제거 할 수 있습니다. 이러한 주석은 사실이 아닌 것을 믿게 만들 수 있습니다. 이것을 충분히 강조하기는 어렵습니다. 아무것도 가정하지 마십시오.

그런 다음 스레드가 다른 것을 잠그려고 시도하는 동안 어떤 잠금이 유지되는지 확인하십시오. 가능하면 나사산이 역순으로 잠금 해제됩니다. 스레드가 한 번에 하나의 잠금 장치 만 보유하도록하는 것이 더 좋습니다.

스레드의 실행을 통해 즉시 작업하고 모든 잠금 이벤트를 검사합니다. 각 잠금에서 스레드가 다른 잠금을 보유하는지 판별하고, 그렇다면 어떤 상황에서 유사한 실행 경로를 수행하는 다른 스레드가 고려중인 잠금 이벤트에 도달 할 수 있습니다.

시간이나 돈이 다 ​​떨어지기 전에 문제를 찾지 못할 수도 있습니다.


4
+1 와우, 그것은 비관적이지만 사실이 아닙니다. 모든 버그를 찾을 수는 없습니다. 제안 해 주셔서 감사합니다!
Michael K

브루스, "진정한 교착 상태"에 대한 당신의 성격이 놀랍습니다. 두 스레드 사이의 교착 상태는 각 스레드가 다른 스레드가 보유한 잠금을 기다리는 경우라고 생각했습니다. 하나의 잠금을 유지하면서 스레드가 현재 다른 스레드가 보유한 두 번째 잠금을 획득하기 위해 대기하는 경우도 정의에 포함됩니다. 그것은 교착 상태처럼 들리지 않습니다. 그래 ??
돈 해치

@DonHatch-나는 그것을 잘못 표현했다. 당신이 묘사하는 상황은 교착 상태가 아닙니다. 잠금 A를 보유한 스레드가 포함 된 상황을 디버깅 한 다음 잠금 B를 확보하려고 시도하는 동안 잠금 B를 보유한 스레드가 잠금 A를 획득하려고 시도하는 혼란을 전하고 싶었습니다. 아마도. 아니면 상황이 훨씬 더 복잡 할 수도 있습니다. 잠금 획득 순서에 대해 매우 열린 마음을 유지해야합니다. 모든 가정을 조사하십시오. 아무것도 믿지 마십시오.
Bruce Ediger

+1은 코드를주의 깊게 읽고 모든 잠금 작업을 단독으로 검사 할 것을 제안합니다. 한 번에 모든 것을 시도하고 보는 것보다 단일 노드를 신중하게 검사하여 복잡한 그래프를 보는 것이 훨씬 쉽습니다. 코드를 쳐다보고 내 머리에서 다른 시나리오를 실행하여 문제를 여러 번 발견했습니다.
Newtopian

11
  1. 다른 사람들이 말했듯이 ... 로깅에 유용한 정보를 얻을 수 있다면 가장 쉬운 방법으로 먼저 시도하십시오.

  2. 관련된 잠금을 식별하십시오. 영원히 기다릴 때까지 기다리는 모든 뮤텍스 / 세마포어를 바꾸십시오 ... 5 분처럼 엄청나게 긴 것. 시간이 초과되면 오류를 기록하십시오. 이것은 적어도 문제와 관련된 자물쇠 중 하나의 방향을 가리킬 것입니다. 타이밍의 가변성에 따라 운이 좋을 수도 있고 몇 번의 실행 후 두 잠금을 모두 찾을 수 있습니다. 함수 대기 코드 / 조건을 사용하여 시간 초과 대기가 처음에 도달 한 방법을 식별하지 못한 후 의사 스택 추적을 로그하십시오. 이것은 문제와 관련된 스레드를 식별하는 데 도움이됩니다.

  3. 시도 할 수있는 또 다른 것은 뮤텍스 / 세마포어 서비스 주위에 래퍼 라이브러리를 작성하는 것입니다. 각 뮤텍스가있는 스레드와 뮤텍스에서 대기중인 스레드를 추적하십시오. 스레드가 얼마나 오래 차단되었는지 확인하는 모니터 스레드를 빌드하십시오. 적절한 기간 동안 트리거하고 추적중인 상태 정보를 덤프하십시오.

어느 시점에서, 오래된 코드 검사가 필요할 것입니다.


6

첫 번째 단계 (Péter가 말한 것처럼)는 로깅입니다. 내 경험상 이것은 종종 문제가된다. 대량 병렬 처리에서는 종종 불가능합니다. 초당 100k의 노드를 처리하는 신경망으로 비슷한 것을 디버깅해야했습니다. 오류는 몇 시간 후에 발생했으며 한 줄의 출력조차도 너무 느려서 며칠이 걸렸습니다. 로깅이 가능하면 데이터가 어느 부분에서 발생하는지 알 때까지 데이터에 집중하지 말고 프로그램 흐름에 더 집중하십시오. 각 함수의 시작 부분에 간단한 줄만 있고 올바른 함수를 찾으면 작은 덩어리로 나눕니다.

또 다른 옵션은 버그를 현지화하기 위해 코드 및 데이터의 일부를 제거하는 것입니다. 어쩌면 일부 클래스 만 가져 와서 가장 기본적인 테스트 만 실행하는 작은 프로그램을 작성할 수도 있습니다 (여전히 여러 스레드에서). 실제 처리 상태에 대한 출력과 같은 모든 GUI를 제거하십시오. (사용자 인터페이스가 종종 버그의 원인이라는 것을 알았습니다.)

코드에서 잠금 초기화와 해제 사이의 완전한 논리적 제어 흐름을 따르십시오. 일반적인 오류는 함수의 시작 부분에서 잠그고 끝 부분에서 잠금을 해제하지만 그 사이에 조건부 리턴 문이있는 것입니다. 예외로 인해 릴리스도 막을 수 있습니다.


"예외는 공개를 막을 수있다"-> 범위 변수가없는 유감스러운 언어 : /
Matthieu M.

1
@Matthieu : 범위가 지정된 변수가 있고 실제로 올바르게 사용하는 것은 두 가지 다른 일이 될 수 있습니다. 그리고 그는 특정 언어를 언급하지 않고 일반적인 문제를 물었습니다. 따라서 이것은 제어 흐름에 영향을 줄 수있는 것입니다.
thorsten müller

3

내 가장 친한 친구는 코드 내 흥미로운 장소에서 인쇄 / 로그 진술을 해왔다. 이것들은 일반적으로 다른 스레드 간의 타이밍을 방해하지 않고 앱 내부에서 실제로 일어나는 일을 더 잘 이해하여 버그를 재현하지 못하게합니다.

그것이 실패하면, 유일한 남은 방법은 코드를 쳐다보고 다양한 스레드와 상호 작용의 정신적 모델을 구축하려고 시도하고 분명히 일어난 일을 달성하기 위해 가능한 미친 방법을 생각하려고합니다 :-) 그러나 나는하지 않습니다 나 자신을 경험이 많은 교착 상태 슬레이어라고 생각하십시오 바라건대 다른 사람들이 더 좋은 아이디어를 줄 수 있기를 바랍니다.


1
나는 오늘 이와 같은 몇 가지 죽은 자물쇠를 디버깅했습니다. 트릭은 잠금을 획득하기 전후에 함수, 행 번호, 파일 이름 및 mutex 변수의 이름을 인쇄하는 매크로로 pthread_mutex_lock ()을 래핑하는 것입니다. pthread_mutex_unlock ()도 동일하게 수행하십시오. 스레드가 얼어 붙은 것을 보았을 때 마지막 두 메시지를보아야 만했습니다. 두 개의 스레드가 잠기려고했지만 절대 끝내지 않았습니다! 이제 남은 것은 런타임에 이것을 토글하는 메커니즘을 추가하는 것입니다. :-)
Plumenator

3

우선, 그 코드의 저자를 얻으십시오. 그는 아마도 자신이 쓴 것을 생각할 것입니다. 두 사람이 대화만으로 문제를 정확히 파악할 수 없더라도 적어도 교착 상태 부분을 찾아내어 도움을받지 않고 코드를 이해하는 것보다 훨씬 빠릅니다.

Péter Török이 말했듯이 로깅은 아마도 길일 것입니다. 내가 아는 한 디버거는 멀티 스레딩 환경에서 나쁜 일을했습니다. 잠금이있는 위치를 찾고 대기중인 자원과 경주 조건이 발생하는 조건을 모두 찾으십시오.


아니요, 로깅은 적입니다. 느리게 로그인하면 로깅이 활성화 된 상태에서 완벽하게 실행되는 프로그램을 쉽게 얻을 수있는 지점으로 프로그램의 동작을 변경하지만 로깅이 꺼지면 교착 상태가 발생합니다. 멀티 코어 CPU가 아닌 단일 프로그램에서 프로그램을 실행할 때와 같은 종류의 문제가 있습니다.
gbjbaanb

@gbjbaanb, 나는 그것이 당신의 적이 너무 가혹하다고 말합니다. 아마도 가장 친한 친구라고 말하는 것이 맞을 것입니다. 이 페이지에서 코드 검사가 실패한 후 로깅이 가장 좋은 첫 번째 단계라고 말한 다른 사람들과 동의 할 것입니다. 문제가 쉽게 해결되었습니다. 그렇지 않으면 반드시 다른 방법에 의지하지만 항상 도움이되지 않기 때문에 작업에 가장 적합한 도구를 사용하지 않는 것이 좋은 조언이라고 생각하지 않습니다.
돈 해치

0

이 질문은 저를 매료시킵니다.) 우선, 매번 실행할 때마다 문제를 일관되게 재현 할 수 있었기 때문에 운이 좋을 것입니다. 매번 동일한 스택 추적으로 동일한 예외를 수신하면 매우 간단합니다. 그렇지 않은 경우 스택 추적을 그다지 신뢰하지 말고 대신 전역 객체에 대한 액세스와 실행 중 상태 변경을 모니터링하십시오.


0

교착 상태를 디버그해야하는 경우 이미 문제가있는 것입니다. 일반적으로 가능한 한 짧은 시간 동안 만 잠금을 사용하거나 전혀 사용하지 마십시오. 잠금을 해제 한 다음 사소한 코드로 이동하는 상황은 피해야합니다.

물론 프로그래밍 환경에 따라 다르지만 단일 스레드에서만 리소스에 액세스 할 수있는 순차 대기열과 같은 것을 봐야합니다.

그리고 레벨 0부터 시작하여 각 잠금에 "레벨"을 할당하십시오. 레벨 0 잠금을 수행하면 다른 잠금이 허용되지 않습니다. 레벨 1 잠금을 수행 한 후에는 레벨 0 잠금을 수행 할 수 있습니다. 레벨 10 잠금을 수행 한 후에는 레벨 9 이하에서 잠금을 수행 할 수 있습니다.

이것이 불가능한 경우 교착 상태에 빠지기 때문에 코드를 수정해야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.