내가 관리하는 새 팀에서 대부분의 코드는 플랫폼, TCP 소켓 및 http 네트워킹 코드입니다. 모든 C ++. 대부분은 팀을 떠난 다른 개발자들로부터 비롯되었습니다. 팀의 현재 개발자는 매우 영리하지만 경험면에서는 대부분 주니어입니다.
가장 큰 문제는 멀티 스레드 동시성 버그입니다. 대부분의 클래스 라이브러리는 일부 스레드 풀 클래스를 사용하여 비동기식으로 작성되었습니다. 클래스 라이브러리의 메소드는 종종 하나의 스레드에서 오래 실행되는 tak을 스레드 풀로 대기열에 넣은 다음 해당 클래스의 콜백 메소드가 다른 스레드에서 호출됩니다. 결과적으로 잘못된 스레딩 가정과 관련된 많은 경우의 버그가 있습니다. 이로 인해 동시성 문제를 방지하기 위해 중요한 섹션과 잠금 장치가 아닌 미묘한 버그가 발생합니다.
이러한 문제를 더욱 어렵게 만드는 것은 수정하려는 시도가 종종 잘못된 것입니다. 내가 시도한 팀의 실수 (또는 레거시 코드 자체)에는 다음과 같은 것이 있습니다.
일반적인 실수 # 1- 공유 데이터를 잠그는 방식으로 동시성 문제를 해결하지만 메소드가 예상 순서대로 호출되지 않을 때 발생하는 일을 잊어 버립니다. 다음은 매우 간단한 예입니다.
void Foo::OnHttpRequestComplete(statuscode status)
{
m_pBar->DoSomethingImportant(status);
}
void Foo::Shutdown()
{
m_pBar->Cleanup();
delete m_pBar;
m_pBar=nullptr;
}
이제 OnHttpNetworkRequestComplete가 발생하는 동안 Shutdown이 호출 될 수있는 버그가 있습니다. 테스터는 버그를 찾고 크래시 덤프를 캡처 한 후 개발자에게 버그를 할당합니다. 그는 차례로 이와 같은 버그를 수정합니다.
void Foo::OnHttpRequestComplete(statuscode status)
{
AutoLock lock(m_cs);
m_pBar->DoSomethingImportant(status);
}
void Foo::Shutdown()
{
AutoLock lock(m_cs);
m_pBar->Cleanup();
delete m_pBar;
m_pBar=nullptr;
}
위의 수정은 훨씬 더 미묘한 경우가 있음을 알 때까지 좋아 보입니다. OnHttpRequestComplete가 다시 호출 되기 전에 Shutdown이 호출되면 어떻게됩니까 ? 우리 팀의 실제 사례는 훨씬 더 복잡하며 코드 검토 프로세스 중에는 가장 어려운 사례를 찾기가 더 어렵습니다.
일반적인 실수 # 2- 잠금을 맹목적으로 종료하여 교착 상태 문제를 해결하고 다른 스레드가 완료 될 때까지 기다렸다가 다시 입력하십시오. 그러나 객체가 다른 스레드에 의해 방금 업데이트 된 경우를 처리하지 않습니다!
일반적인 실수 # 3- 객체가 참조 카운트 되더라도 종료 시퀀스는 포인터를 "해제"합니다. 그러나 여전히 실행중인 스레드가 인스턴스를 해제하기를 기다리는 것을 잊어 버립니다. 따라서 구성 요소가 완전히 종료 된 후 더 이상 호출을 기대하지 않는 상태에서 객체에 대한 스퓨리어스 또는 지연 콜백이 호출됩니다.
다른 경우가 있지만 결론은 다음과 같습니다.
똑똑한 사람들에게도 멀티 스레드 프로그래밍은 매우 어렵습니다.
이러한 실수를 겪으면서 더 적절한 수정을 개발하는 데있어 각 개발자의 오류에 대해 토론하는 데 시간을 보냅니다. 그러나 나는 "올바른"수정이 만질 수있는 엄청난 양의 레거시 코드로 인해 각 문제를 해결하는 방법에 대해 종종 혼란스러워하고 있다고 생각합니다.
곧 출시 될 예정이며 적용 할 패치가 곧 출시 될 예정입니다. 그런 다음 코드베이스를 개선하고 필요한 경우 리팩터링 할 시간이 있습니다. 우리는 모든 것을 다시 쓸 시간이 없습니다. 그리고 대부분의 코드가 그렇게 나쁘지는 않습니다. 그러나 스레딩 문제를 완전히 피할 수 있도록 코드를 리팩터링하려고합니다.
내가 고려하고있는 한 가지 접근법은 이것입니다. 각각의 중요한 플랫폼 기능마다 모든 이벤트 및 네트워크 콜백이 마샬링되는 전용 단일 스레드가 있습니다. 메시지 루프를 사용하는 Windows의 COM 아파트 스레딩과 유사합니다. 긴 블로킹 조작이 여전히 작업 풀 스레드로 전달 될 수 있지만 구성 요소 스레드에서 완료 콜백이 호출됩니다. 구성 요소가 동일한 스레드를 공유 할 수도 있습니다. 그런 다음 스레드 내부에서 실행되는 모든 클래스 라이브러리는 단일 스레드 세계를 가정하여 작성할 수 있습니다.
그 길을 가기 전에 다중 스레드 문제를 처리하기위한 다른 표준 기술이나 디자인 패턴이 있는지에 관심이 있습니다. 그리고 나는 뮤텍스와 세마포어의 기본을 설명하는 책 이외의 것을 강조해야합니다. 어떻게 생각해?
또한 리팩토링 프로세스를 향한 다른 접근 방식에도 관심이 있습니다. 다음 중 하나를 포함 :
실 주위의 디자인 패턴에 관한 문헌 또는 논문. 뮤텍스와 세마포어 소개 이외의 것. 다른 스레드의 비동기 이벤트를 올바르게 처리 할 수 있도록 객체 모델을 설계하는 방}만으로는 대규모 병렬 처리가 필요하지 않습니다 .
다양한 구성 요소의 스레딩을 다이어그램으로 표시하여 솔루션을 쉽게 연구하고 발전시킬 수 있습니다. (즉, 객체와 클래스에서 스레드를 논의하기위한 UML에 해당)
멀티 스레드 코드 문제에 대한 개발 팀 교육
당신은 무엇을 하시겠습니까?