TThread의 "동기화"를 사용하는 것이 더 낫습니까? 아니면 메인 스레드와 하위 스레드 사이에 IPC용 Window 메시지를 사용하는 것이 더 낫습니까?

StackOverflow https://stackoverflow.com/questions/1806339

  •  05-07-2019
  •  | 
  •  

문제

Delphi 2007로 작성된 다소 간단한 멀티스레드 VCL GUI 애플리케이션이 있습니다.기본 양식의 그리드 컨트롤을 업데이트해야 하는 여러 하위 스레드(최대 16개 동시)에서 일부 처리를 수행합니다(단순히 그리드에 문자열 게시).하위 스레드 중 어느 것도 서로 대화하지 않습니다.

내 초기 디자인에는 전화가 포함되었습니다. TThread의 "동기화" 현재 실행 중인 스레드 내에서 그리드 컨트롤 양식을 업데이트합니다.그러나 동기화 호출은 기본적으로 호출 시 기본 스레드인 것처럼 실행된다는 것을 이해합니다.한 번에 최대 16개의 스레드가 실행되고(대부분의 하위 스레드 처리 시간은 1초 미만에서 ~10초까지 소요됨) Window Messages가 더 나은 디자인이 될까요?

하위 스레드가 Windows 메시지(여러 문자열의 레코드로 구성됨)를 게시하고 기본 스레드에 리스너가 있으며 메시지가 수신되면 그리드를 업데이트하는 이 시점에서 작동하도록 했습니다.

이 상황에서 IPC를 위한 최선의 방법에 대한 의견이 있습니까?창 메시지 또는 '동기화'?

창 메시지를 사용하는 경우 그리드에 게시하는 코드를 TCriticalSection(입력 및 나가기) 블록으로 래핑하도록 제안하시겠습니까?아니면 메인 스레드의 그리드에 작성하고 있기 때문에 스레드 안전에 대해 걱정할 필요가 없습니까(비록 창 메시지 핸들러의 기능 내에서라도)?

도움이 되었습니까?

해결책

편집하다:

Delphi 4 및 5(대부분의 작업에 여전히 사용하고 있는 Delphi 버전) 이후 구현 세부 사항 중 많은 부분이 변경된 것으로 보이며 Allen Bauer는 다음과 같이 설명했습니다.

D6 이후로 TThread는 더 이상 SendMessage를 사용하지 않습니다.메인 스레드를 위한 "작업"이 배치되는 스레드로부터 안전한 작업 대기열을 사용합니다.작업이 사용 가능하고 백그라운드 스레드가 이벤트를 차단함을 나타내는 메시지가 기본 스레드에 게시됩니다.기본 메시지 루프가 유휴 상태가 되려고 하면 "CheckSynchronize"를 호출하여 대기 중인 작업이 있는지 확인합니다.그렇다면 이를 처리합니다.작업 항목이 완료되면 백그라운드 스레드가 차단되는 이벤트가 완료를 나타내도록 설정됩니다.D2006 기간에 도입된 차단되지 않는 TThread.Queue 메서드가 추가되었습니다.

수정해 주셔서 감사합니다.따라서 소금 한 알로 원래 답변의 세부 사항을 고려하십시오.

그러나 이것은 실제로 핵심 포인트에 영향을 미치지 않습니다.나는 아직도 그 생각 전체가 Synchronize() 치명적인 결함이 있으며 이는 현대 기계의 여러 코어를 점유하려고 시도하는 순간 명백해질 것입니다.스레드를 "동기화"하지 말고 완료될 때까지 작동하도록 두십시오.그들 사이의 모든 종속성을 최소화하려고 노력하십시오.특히 GUI를 업데이트할 때 절대적으로 아니요 이것이 완료될 때까지 기다려야 하는 이유.이든 Synchronize() 용도 SendMessage() 또는 PostMessage(), 결과 도로 블록은 동일합니다.


여기서 제시하는 내용은 전혀 대안이 아닙니다. Synchronize() 용도 SendMessage() 내부적으로.따라서 발에 총을 쏘기 위해 어떤 무기를 사용하고 싶은지 더 많은 질문이 있습니다.

Synchronize() 도입 이후부터 우리와 함께 해왔습니다. TThread Delphi 2 VCL에서는 VCL의 더 큰 디자인 오류 중 하나이기 때문에 정말 부끄러운 일입니다.

어떻게 작동하나요?그것은 SendMessage() 메인 스레드에서 생성된 창을 호출하고, 호출할 매개변수 없는 객체 메서드의 주소를 전달하도록 메시지 매개변수를 설정합니다.Windows 메시지는 대상 창을 생성하고 해당 메시지 루프를 실행하는 스레드에서만 처리되므로 스레드가 일시 중지되고 기본 VCL 스레드의 컨텍스트에서 메시지를 처리하며 메서드를 호출하고 메서드 이후에만 스레드를 다시 시작합니다. 실행을 마쳤습니다.

그렇다면 무엇이 문제인가요? SendMessage() 곧장)?몇 가지 사항:

  • 임의의 스레드가 다른 스레드의 컨텍스트에서 코드를 실행하도록 강제하면 두 개의 스레드 컨텍스트 전환이 강제로 수행되어 CPU 주기가 불필요하게 소모됩니다.
  • VCL 스레드가 동기화된 메서드를 호출하기 위해 메시지를 처리하는 동안 다른 메시지는 처리할 수 없습니다.
  • 둘 이상의 스레드가 이 방법을 사용하면 모두 차단하고 기다려 Synchronize() 또는 SendMessage() 돌려 주다.이로 인해 거대한 병목 현상이 발생합니다.
  • 교착상태가 발생하기를 기다리고 있습니다.스레드가 호출하는 경우 Synchronize() 또는 SendMessage() 동기화 객체를 보유하는 동안, 메시지를 처리하는 동안 VCL 스레드는 애플리케이션이 잠길 동일한 동기화 객체를 획득해야 합니다.
  • 스레드 핸들을 기다리는 API 호출에 대해서도 마찬가지입니다. WaitForSingleObject() 또는 WaitForMultipleObjects() 스레드가 다른 스레드와 "동기화"하기 위해 이러한 방법이 필요한 경우 메시지를 처리할 수단이 없으면 교착 상태가 발생합니다.

그렇다면 대신 무엇을 사용해야 할까요?몇 가지 옵션에 대해 설명하겠습니다.

  • 사용 PostMessage() 대신에 SendMessage() (또는 PostThreadMessage() 두 스레드가 모두 VCL 스레드가 아닌 경우).하지만 메시지가 도착할 때 더 이상 유효하지 않은 메시지 매개변수의 데이터를 사용하지 않는 것이 중요합니다. 왜냐하면 송신 및 수신 스레드가 전혀 동기화되지 않기 때문입니다. 따라서 문자열이 일치하는지 확인하기 위해 다른 수단을 사용해야 합니다. , 개체 참조 또는 메모리 청크는 전송 스레드가 더 이상 존재하지 않더라도 메시지가 처리될 때 여전히 유효합니다.

  • 스레드로부터 안전한 데이터 구조를 생성하고, 작업자 스레드에서 데이터를 저장하고, 기본 스레드에서 사용합니다.사용 PostMessage() 처리할 새 데이터가 도착했음을 VCL 스레드에 알리고 매번 메시지를 게시하지는 않습니다.연속적인 데이터 스트림이 있는 경우 VCL 스레드가 데이터에 대해 폴링하도록 할 수도 있지만(타이머를 사용하여) 이는 가난한 사람의 버전일 뿐입니다.

  • 더 이상 낮은 수준의 도구를 사용하지 마십시오.최소한 Delphi 2007을 사용 중이라면 다음을 다운로드하십시오. 옴니스레드라이브러리 스레드가 아닌 작업 측면에서 생각하기 시작합니다.이 라이브러리에는 스레드 간 데이터 교환과 동기화를 위한 많은 기능이 있습니다.또한 스레드 풀 구현이 있는데 이는 좋은 점입니다. 사용해야 하는 스레드 수는 응용 프로그램뿐만 아니라 응용 프로그램이 실행되는 하드웨어에 따라 달라지므로 런타임에만 많은 결정을 내릴 수 있습니다.OTL을 사용하면 스레드 풀 스레드에서 작업을 실행할 수 있으므로 시스템은 런타임 시 동시 스레드 수를 조정할 수 있습니다.

편집하다:

다시 읽으면서 나는 당신이 사용할 의도가 없다는 것을 깨달았습니다. SendMessage() 하지만 PostMessage() - 글쎄, 위의 내용 중 일부는 그러면 적용되지 않지만 그대로 두겠습니다.그러나 귀하의 질문에 제가 다루고 싶은 몇 가지 요점이 더 있습니다.

한 번에 최대 16개의 스레드가 실행되고(대부분의 하위 스레드 처리 시간은 1초 미만에서 ~10초까지 소요됨) Window Messages가 더 나은 디자인이 될까요?

매초마다 한 번씩 또는 더 오랜 기간 동안 각 스레드에서 메시지를 게시하면 디자인에 문제가 없습니다.Windows 메시지 대기열의 길이는 유한하고 사용자 지정 메시지는 일반적인 메시지 처리를 너무 많이 방해해서는 안 되기 때문에 초당 스레드당 수백 개 이상의 메시지를 게시하면 안 됩니다(프로그램이 응답하지 않는 것처럼 보이기 시작함).

하위 스레드가 Windows 메시지를 게시하는 경우(여러 문자열의 레코드로 구성됨)

창 메시지에는 레코드가 포함될 수 없습니다.두 가지 매개변수 중 하나를 전달합니다. WPARAM, 다른 유형 LPARAM.이러한 레코드에 대한 포인터를 이러한 유형 중 하나로만 캐스팅할 수 있으므로 레코드의 수명을 어떻게든 관리해야 합니다.동적으로 할당하는 경우에도 해제해야 하므로 오류가 발생하기 쉽습니다.스택의 레코드나 개체 필드에 대한 포인터를 전달하는 경우 메시지가 처리될 때 포인터가 여전히 유효한지 확인해야 합니다. 이는 보낸 메시지보다 게시된 메시지에 더 어렵습니다.

내가 그리드에 게시하는 코드를 TCriticalSection(입력 및 나가기) 블록으로 래핑하는 것을 제안하시겠습니까?아니면 메인 스레드의 그리드에 작성하고 있기 때문에 스레드 안전에 대해 걱정할 필요가 없습니까(비록 창 메시지 핸들러의 기능 내에서라도)?

이렇게 할 필요는 없습니다. PostMessage() 호출이 즉시 반환되므로 동기화가 필요하지 않습니다. 이 지점에서.스레드 안전에 대해 확실히 걱정해야 하지만 불행히도 알 수는 없습니다. 언제.데이터에 대한 액세스가 스레드로부터 안전한지 확인해야 합니다. 언제나 동기화 개체를 사용하여 액세스할 데이터를 잠급니다.레코드에 대해 이를 달성할 수 있는 방법은 실제로 없으며 데이터에 항상 직접 액세스할 수 있습니다.

다른 팁

BTW, 당신은 또한 사용할 수 있습니다 TThread.Queue() 대신에 TThread.Synchronize(). Queue() 비동기 버전은 호출 스레드를 차단하지 않습니다.

(Queue D8 이후에 사용할 수 있습니다).

나는 선호한다 Synchronize() 또는 Queue(), 다른 프로그래머를 위해 이해하기가 훨씬 쉽고 (다른 프로그래머의 경우) 일반 메시지를 보내는 것보다 더 나은 oo (그것에 대한 통제가 없거나 디버깅 할 수 있습니다!)

나는 올바른 방법과 잘못된 방법이 있다고 확신하지만. 나는 두 가지 방법을 모두 사용하여 코드를 작성했으며 내가 계속 돌아 오는 방법은 SendMessage 메소드이며 이유는 확실하지 않습니다.

SendMessage와 Synchronize의 사용은 실제로 아무런 차이를 만들지 않습니다. 두 가지 모두 본질적으로 동일합니다. SendMessage를 계속 사용하는 이유는 더 많은 양의 제어를 인식하기 때문이라고 생각하지만 모르겠습니다.

SendMessage 루틴은 호출 스레드가 일시 중지되고 대상 창이 전송 된 메시지 처리가 완료 될 때까지 기다립니다. 이로 인해 기본 앱 스레드는 본질적으로 통화 기간 동안 호출 하위 스레드와 동기화됩니다. Windows 메시지 처리기에서 중요한 섹션을 사용할 필요가 없습니다.

데이터 전송은 본질적으로 호출 스레드에서 기본 응용 프로그램 스레드에 이르기까지 한 가지 방법입니다. 메시지에서 정수 유형 값을 반환 할 수 있지만 메인 스레드의 메모리 객체를 가리키는 것은 없습니다.

두 스레드가 "동기화"되기 때문에 해당 지점과 기본 앱 스레드가 현재 SendMessage에 응답하여 다른 스레드가 들어오고 동시에 데이터를 쓰는 것에 대해 걱정할 필요가 없습니다. 따라서 중요 섹션이나 다른 유형의 스레드 안전 조치를 사용하는 것에 대해 걱정할 필요가 없습니다.

간단한 사항의 경우 단일 메시지 (wm_threadmsg1)를 정의하고 WPARAM 및 LPARAM 필드를 사용하여 상태 메시지를 전송 (정수) 상태로 전송할 수 있습니다. 보다 복잡한 예를 보려면 LPARAM을 통해 전달하여 Longint로 다시 캐스팅하여 문자열을 전달할 수 있습니다. A-La Longint (PCHAR (Myvar)) 또는 D2009 또는 새로 사용하는 경우 PwideChar를 사용하십시오.

이미 동기화 메소드로 작업 한 경우 변경을 위해 재 작업하는 것에 대해 걱정하지 않을 것입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top