멀티 스레드 버그에 시달리고


26

내가 관리하는 새 팀에서 대부분의 코드는 플랫폼, 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 아파트 스레딩과 유사합니다. 긴 블로킹 조작이 여전히 작업 풀 스레드로 전달 될 수 있지만 구성 요소 스레드에서 완료 콜백이 호출됩니다. 구성 요소가 동일한 스레드를 공유 할 수도 있습니다. 그런 다음 스레드 내부에서 실행되는 모든 클래스 라이브러리는 단일 스레드 세계를 가정하여 작성할 수 있습니다.

그 길을 가기 전에 다중 스레드 문제를 처리하기위한 다른 표준 기술이나 디자인 패턴이 있는지에 관심이 있습니다. 그리고 나는 뮤텍스와 세마포어의 기본을 설명하는 책 이외의 것을 강조해야합니다. 어떻게 생각해?

또한 리팩토링 프로세스를 향한 다른 접근 방식에도 관심이 있습니다. 다음 중 하나를 포함 :

  1. 실 주위의 디자인 패턴에 관한 문헌 또는 논문. 뮤텍스와 세마포어 소개 이외의 것. 다른 스레드의 비동기 이벤트를 올바르게 처리 할 수 ​​있도록 객체 모델을 설계하는 방}만으로는 대규모 병렬 처리가 필요하지 않습니다 .

  2. 다양한 구성 요소의 스레딩을 다이어그램으로 표시하여 솔루션을 쉽게 연구하고 발전시킬 수 있습니다. (즉, 객체와 클래스에서 스레드를 논의하기위한 UML에 해당)

  3. 멀티 스레드 코드 문제에 대한 개발 팀 교육

  4. 당신은 무엇을 하시겠습니까?


23
어떤 사람들은 문제에 직면했을 때 멀티 스레딩을 사용할 것이라고 생각합니다. 이제 그들은 twolm 프로브를 가지고
톰 종복

20
나를 위해 잘 작동하는 것은 가능할 때마다 가변성을 제거하는 것입니다 . 새로운 값을 전달하기 위해 가변 객체 변경 상태를 볼 때 변경된 값을 보유 하는 새로운 불변 객체 를 전달하도록 리팩토링하려고합니다 . 객체 초기화 안전하게 수행 할 경우,이 보증 데이터 종족의 부재 - 상당히 완화
모기

3
멀티 스레드 지옥에 오신 것을 환영합니다. 저는 Ada, Occam, C ++에서 20 년 이상 멀티 스레드 / 병렬 프로그램을 작성해 왔습니다. 결코 쉬운 일이 아니며 모든 것에 매우 신중한 생각이 필요하며 "쉽게 X를 수행하십시오"라고 말하는 사람은 실제로 무슨 일이 일어나고 있는지 이해하지 못하는 바보입니다. 행운을 빕니다.
quick_now

2
동시성을 잘보고 싶다면 Erlang을 사용하십시오! 사실 당신이 원하는 것은 이상한 모퉁이가 제거 될 공유 무 배우 모델의 형태입니다.
Zachary K

3
@DeadMG 나는 공유 상태 동시성이 본질적으로 이상한 경우에 발생하기 쉽기 때문에 피해야한다고 주장한다. 하지만 안녕하세요, Erlang에 관한 책을 썼습니다
Zachary K

답변:


27

코드에는 외에도 중요한 다른 문제가 있습니다. 포인터를 수동으로 삭제 하시겠습니까? cleanup함수를 호출 ? 아야 또한 질문 의견에서 정확하게 지적했듯이 잠금에 RAII를 사용하지 마십시오. 이는 또 다른 장엄한 실패이며 DoSomethingImportant예외가 발생할 때 끔찍한 일이 발생하도록 보장합니다 .

이 멀티 스레드 버그가 발생한다는 사실은 코어의 단지 증상이 코드를 가지고 problem-입니다 매우 나쁜 에서 의미 있는 스레딩 상황과 완전히 신뢰할 수없는 도구와 전 숙어를 사용하고 있습니다. 내가 당신이라면, 그것이 하나의 스레드로 작동한다는 사실에 놀랄 것입니다.

일반적인 실수 # 3-객체가 참조 카운트 되더라도 종료 시퀀스는 포인터를 "해제"합니다. 그러나 여전히 실행중인 스레드가 인스턴스를 해제하기를 기다리는 것을 잊어 버립니다. 따라서 구성 요소가 완전히 종료 된 후 더 이상 호출을 기대하지 않는 상태에서 객체에 대한 스퓨리어스 또는 지연 콜백이 호출됩니다.

참조 카운트의 요점은 스레드가 이미 인스턴스를 해제 했다는 입니다. 그렇지 않은 경우 스레드에 여전히 참조가 있기 때문에 삭제할 수 없습니다.

사용하십시오 std::shared_ptr. 모든 스레드가 발표했다 (그리고 때 아무도 따라서, 그들이에는 포인터가 없기 때문에 함수를 호출 할 수 없다), 다음 소멸자가 호출됩니다. 이것은 안전합니다.

둘째, Intel의 Thread Building Blocks 또는 Microsoft의 Parallel Patterns Library와 같은 실제 스레딩 라이브러리를 사용하십시오. 직접 작성하는 것은 시간이 걸리고 신뢰할 수 없으며 코드에는 필요하지 않은 스레딩 세부 정보가 가득합니다. 자신의 잠금을 수행하는 것은 자신의 메모리 관리를 수행하는 것만 큼 나쁩니다. 그들은 이미 귀하의 사용에 올바르게 작동하는 많은 범용 매우 유용한 스레딩 관용구를 구현했습니다.


이것은 괜찮은 대답이지만 내가 찾고있는 방향은 아닙니다. 단순화를 위해 작성된 샘플 코드를 평가하는 데 너무 많은 시간을 소비하기 때문입니다 (제품의 실제 코드를 반영하지 않음). 그러나 "믿을 수없는 도구"라는 한 가지 의견이 궁금합니다. 신뢰할 수없는 도구는 무엇입니까? 어떤 도구를 추천하십니까?
koncurrency

5
@koncurrency : 신뢰할 수없는 도구는 수동 메모리 관리 또는 이론적으로 문제 X를 해결하는 자체 동기화 작성과 같은 것입니다. 그러나 실제로는 너무 잘못되어 거대한 실수를 보장하고 문제를 해결할 수있는 유일한 방법입니다 합리적인 규모의 현재는 개발자 시간에 대한 대규모의 불균형 한 투자입니다.
DeadMG

9

다른 포스터는 핵심 문제를 해결하기 위해 수행해야 할 작업에 대해 잘 설명했습니다. 이 글은 레거시 코드를 패치하는보다 즉각적인 문제에 대해 다룰 것입니다. 다시 말해, 이것은 일을 하는 올바른 방법이 아니며, 단지 지금은 절뚝 거리는 방법입니다.

주요 이벤트를 통합하려는 아이디어는 좋은 출발입니다. 순서 의존성이있는 곳이라면 모든 단일 키 동기화 이벤트를 처리하기 위해 단일 디스패치 스레드를 사용하려고합니다. 스레드 안전 메시지 대기열을 설정하고 현재 동시성에 민감한 작업 (할당, 정리, 콜백 등)을 수행하는 경우 대신 해당 스레드에 메시지를 보내어 작업을 수행하거나 트리거하도록합니다. 아이디어는이 스레드가 모든 작업 단위 시작, 중지, 할당 및 정리를 제어한다는 것입니다.

디스패치 스레드는 설명 된 문제점을 해결 하지 않고 한 곳에서 통합합니다. 예기치 않은 순서로 발생하는 이벤트 / 메시지에 대해 여전히 걱정해야합니다. 런타임이 많은 이벤트는 여전히 다른 스레드로 전송해야하므로 공유 데이터의 동시성에 여전히 문제가 있습니다. 이를 완화하는 한 가지 방법은 참조로 데이터를 전달하지 않는 것입니다. 가능하면 발송 메시지의 데이터는 수신자가 소유 한 사본이어야합니다. (이것은 다른 사람들이 언급 한 데이터를 불변으로 만드는 선을 따릅니다.)

이 디스패치 접근 방식의 장점은 디스패치 스레드 내에서 특정 작업이 순차적으로 발생하고 있음을 적어도 알고있는 일종의 세이프 헤븐을 가지고 있다는 것입니다. 단점은 병목 현상과 추가 CPU 오버 헤드가 발생한다는 것입니다. 처음에는 이러한 것들에 대해 걱정하지 말 것을 제안합니다. 디스패치 스레드로 최대한 많이 이동하여 먼저 올바른 작업을 측정하는 데 중점을 둡니다. 그런 다음 프로파일 링을 수행하여 CPU 시간을 가장 많이 차지하는 것을 확인하고 올바른 멀티 스레딩 기술을 사용하여 디스패치 스레드에서 다시 전환하십시오.

다시 말하지만, 내가 설명하는 것은 일을하는 올바른 방법이 아니라 상업 마감 시간을 충족시키기에 충분한 작은 단위로 올바른 방향으로 이동할 수있는 프로세스입니다.


기존 도전을 극복하기위한 합리적인 중간 제안을 +1하십시오.

예, 이것이 제가 조사하는 접근법입니다. 성능에 대해 좋은 지적을합니다.
koncurrency

단일 디스패치 스레드를 통과하도록 변경하는 것은 빠른 패치처럼 들리지 않지만 오히려 대규모 리 팩터입니다.
Sebastian Redl

8

표시된 코드를 기반으로 WTF 더미가 있습니다. 잘못 작성된 다중 스레드 응용 프로그램을 증분 방식으로 수정하는 것은 불가능하지는 않지만 매우 어렵습니다. 소유자에게 응용 프로그램이 중대한 재 작업 없이는 신뢰할 수 없을 것이라고 말하십시오. 공유 객체와 상호 작용하는 코드의 모든 비트를 검사하고 재 작업을 기반으로 견적을 제공하십시오. 먼저 검사에 대한 견적을 제공하십시오. 그런 다음 재 작업에 대한 견적을 제공 할 수 있습니다.

코드를 재 작업 할 때 코드가 올바르게 수정되도록 코드를 작성해야합니다. 그렇게하는 방법을 모르는 사람을 찾으면 같은 장소에있게됩니다.


내 대답이 상향 조정 된 후 지금 읽으십시오. 그냥 입문 문장을 좋아 한다고 말하고 싶었습니다 :)
back2dos

7

애플리케이션 리팩토링에 전념 할 시간이 있다면 액터 모델을 살펴 보는 것이 좋습니다 (예 : Theron , Casablanca , libcppa , CAF for C ++ 구현 참조).

액터는 비동기식 메시지 교환을 통해서만 동시에 실행되고 서로 통신하는 객체입니다. 따라서 스레드 관리, 뮤텍스, 교착 상태 등의 모든 문제는 액터 구현 라이브러리에서 처리되며 객체 (액터)의 동작을 구현하는 데 집중할 수 있습니다.

  1. 메시지 받기
  2. 계산 수행
  3. 메시지를 보내거나 다른 행위자를 만들거나 죽입니다.

한 가지 접근 방식은 먼저 주제에 대한 독서 를하고 액터 모델이 코드에 통합 될 수 있는지 확인하기 위해 하나 또는 두 개의 라이브러리를 살펴 보는 것입니다.

나는 몇 달 동안 내 프로젝트 에서이 모델을 (간단한 버전) 사용 해 왔으며 얼마나 강력한 지 놀랐습니다.


1
스칼라를위한 Akka 라이브러리는 이것을 구현 한 것으로, 아이들이 죽을 때 부모 배우를 죽이는 법, 또는 그 반대로하는 법을 많이 생각합니다. 나는 그것이 C ++이 아니라는 것을 안다 : akka.io
GlenPeterson

1
@GlenPeterson : 고마워, 나는 akka (현재 가장 흥미로운 솔루션을 고려하고 Java와 Scala와 함께 작동)에 대해 알고 있지만 질문은 C ++을 구체적으로 다루고 있습니다. 그렇지 않으면 Erlang을 고려할 수 있습니다. Erlang에서는 멀티 스레딩 프로그래밍의 모든 문제가 사라 졌다고 생각합니다. 그러나 akka와 같은 프레임 워크는 매우 가깝습니다.
Giorgio

"Erlang에서는 멀티 스레딩 프로그래밍의 모든 문제가 사라 졌다고 생각합니다." 아마도 이것이 조금 과장된 것 같습니다. 또는 사실이면 성능이 저하 될 수 있습니다. Akka는 C ++에서 작동하지 않으며 다중 스레드 관리를위한 최첨단처럼 보입니다. 그러나 스레드 안전하지는 않습니다. 여전히 배우들 사이에서 변경 가능한 상태를 전달하고 발로 자신을 쏠 수 있습니다.
GlenPeterson

저는 Erlang 전문가는 아니지만 AFAIK 각 행위자는 격리되어 실행되며 변경 불가능한 메시지가 교환됩니다. 따라서 실제로 스레드 및 공유 가능한 변경 가능 상태를 처리 할 필요가 없습니다. 성능은 C ++보다 낮을 수 있지만 추상화 수준을 높이면 항상 발생합니다 (실행 시간은 늘리지 만 개발 시간은 줄입니다).
Giorgio

downvoter가 의견을 남기고 어떻게이 답변을 개선 할 수 있는지 제안 할 수 있습니까?
Giorgio

6

일반적인 실수 # 1- 공유 데이터를 잠그는 방식으로 동시성 문제를 해결하지만 메소드가 예상 순서대로 호출되지 않을 때 발생하는 일을 잊어 버립니다. 다음은 매우 간단한 예입니다.

여기서 실수는 "잊어 버린"것이 아니라 "고치지 않는 것"입니다. 예기치 않은 순서로 문제가 발생하면 문제가있는 것입니다. 문제를 해결하는 대신 문제를 해결해야합니다 (물건에 자물쇠를 치는 것이 일반적으로 해결 방법입니다).

액터 모델 / 메시징을 어느 정도 적용하고 관심을 분리하도록 노력해야합니다. 의 역할 Foo은 분명히 어떤 종류의 HTTP 통신을 처리하는 것입니다. 이 작업을 병렬로 수행하도록 시스템을 설계하려면 위의 계층이 객체 수명주기를 처리하고 그에 따라 동기화에 액세스해야합니다.

동일한 변경 가능한 데이터에서 여러 스레드가 작동하도록하는 것은 어렵습니다. 그러나 거의 필요하지 않습니다. 이를 요구하는 모든 일반적인 사례는 이미 관리하기 쉬운 개념으로 추상화되어 주요 명령 언어에 대해 여러 번 구현되었습니다. 당신은 그들을 사용해야합니다.


2

귀하의 문제는 꽤 나쁘지만 C ++을 제대로 사용하지 않는 것이 일반적입니다. 코드 검토는 이러한 문제 중 일부를 해결합니다. 30 분 동안 한 세트의 안구가 결과의 90 %를 기록합니다. (이에 대한 인용은 Google 검색 가능)

# 1 문제 잠금 교착 상태를 방지하기 위해 엄격한 잠금 계층이 있는지 확인해야합니다.

자동 잠금을 래퍼 및 매크로로 바꾸면 이렇게 할 수 있습니다.

랩퍼 뒷면에 작성된 잠금의 정적 글로벌 맵을 유지하십시오. 매크로를 사용하여 상세 이름 및 행 번호 정보를 Autolock 랩퍼 생성자에 삽입하십시오.

정적 지배자 그래프도 필요합니다.

이제 잠금 내부에서 도미네이터 그래프를 업데이트해야하며 주문 변경이 있으면 오류가 발생하여 중단됩니다.

광범위한 테스트 후 잠재적 인 교착 상태를 대부분 제거 할 수 있습니다.

코드는 학생을위한 연습으로 남겨집니다.

문제 # 2는 사라질 것입니다. (대부분)

당신의 전형적인 솔루션이 작동 할 것입니다. 나는 그것을 미션 및 생명 크리티컬 시스템에서 이전에 사용했습니다. 내 취향은 이것입니다

  • 통과하기 전에 불변의 물체를 전달하거나 복사하십시오.
  • 공용 변수 또는 게터를 통해 데이터를 공유하지 마십시오.

  • 외부 이벤트는 다중 스레드 디스패치를 ​​통해 하나의 스레드가 서비스하는 큐로 들어옵니다. 이제 이벤트 처리에 대한 일종의 이유가 있습니다.

  • 스레드 간 교차 데이터 변경은 스레드 안전 큐로 들어오고 하나의 스레드로 처리됩니다. 구독하십시오. 이제 데이터 흐름에 대한 일종의 이유를 알 수 있습니다.

  • 데이터가 다른 지역으로 이동해야하는 경우 데이터를 데이터 대기열에 게시하십시오. 그러면 복사하여 구독자에게 무조건 전달합니다. 또한 프로그램의 모든 데이터 종속성을 해제합니다.

이것은 거의 싼 배우 모델입니다. 조르지오의 링크가 도움이 될 것입니다.

마지막으로 종료 오브젝트에 대한 문제점.

참조 횟수를 계산할 때 50 %가 해결되었습니다. 다른 50 %는 참조 횟수 콜백입니다. 콜백 소지자에게 참조를 전달하십시오. 그런 다음 종료 호출은 참조 카운트에서 0 카운트를 기다려야합니다. 복잡한 객체 그래프를 해결하지 않습니다. 그것은 실제 가비지 수집에 들어가고 있습니다. (finalize ()가 언제 호출되는지에 대한 약속을하지 않는 Java의 동기입니다. 그러면 프로그래밍에서 벗어날 수 있습니다.)


2

미래의 탐험가들을 위해 : 액터 모델에 대한 답변을 보완하기 위해 CSP ( 순차 프로세스 통신) 를 추가하고 싶습니다 . 더 큰 프로세스 계산법에 대한 끄덕임이 있습니다. CSP는 액터 모델과 비슷하지만 다르게 분할됩니다. 여전히 많은 스레드가 있지만 서로 구체적으로 통신하지 않고 특정 채널을 통해 통신하며 두 프로세스는 각각 발생하기 전에 각각 송수신 할 준비가되어 있어야합니다. CSP 코드 의 정확성 을 입증 하기 위한 공식화 된 언어 도 있습니다 . 나는 여전히 CSP를 많이 사용하는 것으로 전환하고 있지만 몇 달 동안 몇 가지 프로젝트에서 CSP를 사용하고 있으며 크게 단순화되었습니다.

켄트 대학교는 (A C ++ 구현이 https://www.cs.kent.ac.uk/projects/ofa/c++csp/ 에서 복제, https://github.com/themasterchef/cppcsp2을 ).


1

실 주위의 디자인 패턴에 관한 문헌 또는 논문. 뮤텍스와 세마포어 소개 이외의 것. 대규모 병렬 처리가 필요하지 않으며 다른 스레드의 비동기 이벤트를 올바르게 처리하기 위해 객체 모델을 설계하는 방법 일뿐입니다.

나는 현재 이것을 읽고 있으며 C ++에서 (새로운 스레딩 라이브러리를 사용하지만 전역 설명이 귀하의 경우에는 유효하다고 생각합니다)에서 얻을 수있는 모든 문제와이를 피하는 방법을 설명합니다 : http : //www.amazon. com / C-Concurrency-Action-Practical-Multithreading / dp / 1933988770 / ref = sr_1_1? ie = UTF8 & qid = 1337934534 & sr = 8-1

다양한 구성 요소의 스레딩을 다이어그램으로 표시하여 솔루션을 쉽게 연구하고 발전시킬 수 있습니다. (즉, 객체와 클래스에서 스레드를 논의하기위한 UML에 해당)

나는 개인적으로 단순화 된 UML을 사용하고 메시지가 비동기 적으로 수행된다고 가정합니다. 또한 이것은 "모듈"사이에 적용되지만 모듈 내부에서는 알 필요가 없습니다.

멀티 스레드 코드 문제에 대한 개발 팀 교육

이 책은 도움이 될 것이지만, 연습 / 프로토 타이핑과 숙련 된 멘토가 더 나을 것이라고 생각합니다.

당신은 무엇을 하시겠습니까?

동시성 문제를 이해하지 못하는 사람들이 프로젝트에서 작업하는 것을 완전히 피할 것입니다. 그러나 나는 당신이 그렇게 할 수 없다고 생각합니다. 따라서 당신의 특정한 경우에는 팀이 더 잘 교육 받도록 노력하는 것 외에는 전혀 모릅니다.


책 제안에 감사드립니다. 나는 그것을 데리러 올 것이다.
koncurrency

스레딩은 정말 어렵습니다. 모든 프로그래머가 도전하는 것은 아닙니다. 비즈니스 세계에서 쓰레드가 사용되는 것을 볼 때마다 두 개의 쓰레드가 동시에 실행될 수없는 방식으로 잠금 장치로 둘러싸였습니다. 쉽게 만들기 위해 따라야 할 규칙이 있지만 여전히 어렵습니다.
GlenPeterson

@GlenPeterson Agreed, 이제 더 많은 경험을 얻었으므로 (이 답변 이후) 관리가 가능하고 데이터 공유를 방해하기 위해 더 나은 추상화가 필요하다는 것을 알았습니다. 운 좋게도 언어 디자이너는이 작업을 열심히하는 것 같습니다.
Klaim

저는 스칼라에 깊은 인상을 받았습니다. 특히 불변성의 기능적 프로그래밍 이점, Java에 대한 부작용은 C ++의 직접적인 자손입니다. Java 가상 머신에서 실행되므로 필요한 성능이 없을 수 있습니다. Joshua Bloch의 저서 인 "Effective Java"는 가변성을 최소화하고 완벽한 인터페이스를 만들고 스레드 안전에 관한 것입니다. Java 기반이지만 C ++에 80-90 %를 적용 할 수 있습니다. 코드 검토에서 가변성 및 공유 상태 (또는 공유 상태의 가변성)에 의문을 제기하는 것이 첫 번째 단계입니다.
GlenPeterson

1

이미 문제를 인정하고 적극적으로 해결책을 찾고 있습니다. 내가 할 일은 다음과 같습니다.

  • 애플리케이션에 대한 스레딩 모델을 설계하십시오. 이 문서는 다음과 같은 질문에 대한 답변을 제공합니다. 어떤 유형의 스레드가 있습니까? 어떤 스레드에서 어떤 작업을 수행해야합니까? 어떤 종류의 동기화 패턴을 사용해야합니까? 다시 말해, 멀티 스레딩 문제를 해결할 때 "약속 규칙"을 설명해야합니다.
  • 스레드 분석 도구를 사용하여 코드베이스에 오류가 있는지 확인하십시오. Valgrind에는 Helgrind 라는 스레드 검사기 가있어 공유 상태와 같은 것들을 적절히 동기화하지 않고 조작하는 데 유용합니다. 가장 좋은 도구가있을 것입니다. 찾아보십시오.
  • C ++에서 마이그레이션을 고려하십시오. C ++은 동시 프로그램을 작성하는 악몽입니다. ​​개인적으로 Erlang을 선택할 것이지만 이는 맛의 문제입니다.

8
마지막 비트의 경우 -1입니다. OP의 코드는 실제 C ++ 도구가 아닌 가장 원시적 인 도구를 사용하고있는 것으로 보입니다.
DeadMG

2
동의하지 않습니다. 적절한 C ++ 메커니즘과 도구를 사용하더라도 C ++의 동시성은 악몽입니다. 그리고 "는 문구를 선택했다 있습니다 생각을 ." 나는 그것이 현실적인 대안이 아닐 수도 있다는 것을 완전히 이해하지만 대안을 고려하지 않고 C ++를 유지하는 것은 단순한 바보입니다.
JesperE

4
@JesperE-죄송합니다. C ++의 동시성은 너무 낮은 수준으로 이동하면 악몽 일뿐입니다. 적절한 스레딩 추상화를 사용하면 다른 언어 나 런타임보다 나쁘지 않습니다. 그리고 적절한 응용 프로그램 구조를 사용하면 실제로 내가 본 것만큼이나 쉽습니다.
Michael Kohne

2
내가 일하는 곳에서는 적절한 응용 프로그램 구조가 있고 올바른 스레딩 추상화 등을 사용한다고 생각합니다. 그럼에도 불구하고, 우리는 수년 동안 버그를 디버깅하는 데 많은 시간을 보냈습니다. 버그는 동시성을 위해 올바르게 설계된 언어로는 나타나지 않습니다. 그러나 나는 우리가 이것에 동의하지 않기로 동의해야한다는 느낌을 받았습니다.
JesperE

1
@JesperE : 동의합니다. Erlang 모델 (Scala / Java, Ruby 및 C ++에 대한 구현이 존재 함)은 스레드로 직접 코딩하는 것보다 훨씬 강력합니다.
조르지오

1

예제보기 : Foo :: Shutdown이 실행을 시작하자마자 OnHttpRequestComplete를 호출하여 더 이상 실행할 수 없어야합니다. 그것은 어떤 구현과도 관련이 없으며 작동하지 않습니다.

또한 OnHttpRequestComplete에 대한 호출이 실행되는 동안 (확실히 true) Foo :: Shutdown을 호출 할 수 없으며 OnHttpRequestComplete에 대한 호출이 여전히 미해결 인 경우에는 호출 할 수 없다고 주장 할 수도 있습니다.

가장 먼저 얻는 것은 잠금 등이 아니라 허용되는 것의 논리입니다. 간단한 모델은 클래스에 0 개 이상의 불완전한 요청, 아직 호출되지 않은 0 개 이상의 완료, 실행중인 0 개 이상의 완료, 개체 종료를 원할 수 있습니다.

Foo :: Shutdown은 완료 완료 실행을 완료하고, 가능한 경우 종료 할 수있는 지점까지 불완전한 요청을 실행하고, 더 이상 완료가 시작되지 않도록하고, 더 많은 요청이 시작되지 않도록해야합니다.

수행 할 작업 : 기능에 수행 할 작업을 정확하게 지정 하는 사양을 추가하십시오 . 예를 들어, 종료가 호출 된 후 http 요청을 시작하지 못할 수 있습니다. 그런 다음 사양을 충족하도록 함수를 작성하십시오.

잠금은 공유 변수의 수정을 제어 할 수있는 가장 작은 시간 동안 만 사용하는 것이 가장 좋습니다. 따라서 잠금으로 보호되는 "performingShutDown"변수가있을 수 있습니다.


0

당신은 무엇을 하시겠습니까?

솔직히; 나는 빨리 도망 갔다.

동시성 문제는 NASTY 입니다. 몇 달 동안 완벽하게 작동 한 다음 (몇 가지 일의 특정 타이밍으로 인해) 갑자기 고객의 얼굴에 날아갔습니다. 어떤 일이 발생했는지 파악할 수 없으며 멋진 (재생 가능한) 버그 보고서를 볼 수 없으며 희망도 없습니다. 심지어 소프트웨어와 관련이없는 하드웨어 결함이 아닌지 확인하십시오.

동시성 문제를 피하기 위해서는 디자인 단계에서 시작해야합니다. 정확히 어떻게 할 것인지 ( "글로벌 잠금 순서", 액터 모델 등)부터 시작해야합니다. 다가오는 릴리스 이후에 모든 것이 스스로 파괴되지 않기를 희망하면서 미친 패닉으로 고치려는 것은 아닙니다.

나는 여기서 농담하지 않습니다. 자신의 말 ( " 대부분의 팀은 팀을 떠난 다른 개발자들로부터 유래했습니다. 팀의 현재 개발자들은 매우 영리하지만 경험의 측면에서 대부분 주니어입니다. ")는 사람들이 이미 경험 한 모든 경험을 나타냅니다 . 제안하고 있습니다.

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