델파이의 스레드 클래스
랩터[멘탈스튜디오]
http://mental.mentu.com
2부
첫 번째는 생성자입니다.
생성자 TThread.Create(CreateSuspended: Boolean);
시작하다
상속됨 생성;
스레드 추가;
FSuspended := CreateSuspended;
FCreateSuspended := CreateSuspended;
FHandle := BeginThread(nil, 0, @ThreadPROc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
FHandle = 0이면
EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(GetLastError)])를 발생시킵니다.
끝;
이 생성자는 코드가 많지는 않지만 여기서 스레드를 생성하기 때문에 가장 중요한 멤버라고 볼 수 있습니다.
Inherited를 통해 TObject.Create를 호출한 후 첫 번째 문장은 AddThread라는 프로세스를 호출하는 것이며 해당 소스 코드는 다음과 같습니다.
프로시저 AddThread;
시작하다
InterlockedIncrement(ThreadCount);
끝;
해당 RemoveThread도 있습니다.
절차 RemoveThread;
시작하다
InterlockedDecrement(ThreadCount);
끝;
그 기능은 매우 간단합니다. 전역 변수를 늘리거나 줄여 프로세스의 스레드 수를 계산하는 것입니다. 여기서는 일반적으로 사용되는 Inc/Dec 프로세스를 사용하여 변수를 늘리거나 줄이는 것이 아니라 InterlockedIncrement/InterlockedDecrement 프로세스 쌍을 사용하여 변수에 1을 더하거나 빼는 기능을 모두 구현합니다. 그러나 가장 큰 차이점은 InterlockedIncrement/InterlockedDecrement가 스레드로부터 안전하다는 것입니다. 즉, 멀티스레딩에서는 올바른 실행 결과를 보장할 수 있지만 Inc/Dec은 그렇지 않습니다. 또는 운영 체제 이론 용어로 이는 한 쌍의 "원시" 작업입니다.
둘 사이의 구현 세부 사항의 차이점을 설명하기 위해 플러스 1을 예로 들어 보겠습니다.
일반적으로 메모리 데이터에 1을 추가하는 작업은 분해 후 세 단계를 거칩니다.
1. 메모리에서 데이터 읽기
2. 데이터 플러스원
3. 메모리에 저장
이제 Inc를 사용하여 2스레드 애플리케이션에서 증분 작업을 수행할 때 발생할 수 있는 상황을 가정해 보겠습니다.
1. 스레드 A가 메모리에서 데이터를 읽습니다(3으로 가정).
2. 스레드 B는 메모리에서 데이터를 읽습니다(또한 3).
3. 스레드 A는 데이터에 1을 추가합니다(현재는 4입니다).
4. 스레드 B는 데이터에 1을 추가합니다(이제는 4이기도 합니다).
5. 스레드 A는 데이터를 메모리에 저장합니다(현재 메모리의 데이터는 4입니다).
6. 스레드 B도 데이터를 메모리에 저장합니다. (메모리의 데이터는 여전히 4이지만 두 스레드 모두 여기에 1을 추가했으며 5가 되어야 하므로 여기서는 잘못된 결과가 있습니다.)
InterlockIncrement 프로세스에는 이러한 문제가 없습니다. 소위 "기본"은 중단할 수 없는 작업이기 때문입니다. 즉, 운영 체제는 "기본"이 실행되기 전에 스레드 전환이 발생하지 않도록 보장할 수 있습니다. 따라서 위의 예에서는 스레드 A가 실행을 마치고 메모리에 데이터를 저장한 후에만 스레드 B가 숫자를 가져와서 추가할 수 있습니다. 이렇게 하면 멀티 스레드 상황에서도 결과가 동일하게 됩니다. 옳은.
이전 예에서는 스레드가 "동기화"(동기화)되어야 하는 이유인 "스레드 액세스 충돌" 상황도 보여줍니다. 이에 대해서는 나중에 동기화를 언급할 때 자세히 설명하겠습니다.
동기화에 대해 말하면 여담이 있습니다. 캐나다 워털루 대학의 Li Ming 교수는 한때 "스레드 동기화"에서 "동기화"로 번역된 동기화라는 단어에 대해 이의를 제기한 적이 있습니다. 합리적인. 중국어로 "동기화"는 "동시에 발생함"을 의미하며 "스레드 동기화"의 목적은 이러한 "동시 발생"을 방지하는 것입니다. 영어로 동기화(Synchronize)에는 두 가지 의미가 있습니다. 하나는 전통적인 의미의 동기화(동시에 발생하다)이고, 다른 하나는 "동시 작동하다(To Operate in unison)"(동시에 작동하다)입니다. "스레드 동기화"에서 동기화라는 단어는 후자의 의미, 즉 "여러 스레드가 동일한 데이터에 액세스할 때 조정을 유지하고 오류를 방지하도록 보장합니다."를 참조해야 합니다. 하지만 IT 업계에서는 아직까지 이런 식으로 부정확하게 번역된 단어가 많기 때문에, 소프트웨어 개발은 세심한 작업이고, 무엇을 명확히 해야 하는지 여기서만 설명하도록 하겠습니다. 절대로 모호해서는 안 됩니다.
TThread의 생성자로 돌아가 보겠습니다. 다음으로 가장 중요한 것은 다음 문장입니다.
FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
앞서 언급한 Delphi RTL 함수 BeginThread가 여기에 사용됩니다. 여기에는 많은 매개변수가 있으며, 핵심 매개변수는 세 번째와 네 번째 매개변수입니다. 세 번째 매개변수는 앞서 언급한 스레드 함수, 즉 스레드에서 실행되는 코드 부분이다. 네 번째 매개변수는 스레드 함수에 전달되는 매개변수입니다. 여기서는 생성된 스레드 개체(예: Self)입니다. 다른 매개변수 중 다섯 번째는 스레드가 생성 후 일시 중단되고 즉시 실행되지 않도록 설정하는 데 사용되며(스레드 시작 작업은 AfterConstruction의 CreateSuspended 플래그에 따라 결정됨) 여섯 번째는 스레드 ID를 반환하는 것입니다.
이제 TThread의 핵심인 스레드 함수 ThreadProc를 살펴보겠습니다. 흥미로운 점은 이 스레드 클래스의 핵심이 스레드의 멤버가 아니라 전역 함수라는 것입니다(BeginThread 프로세스의 매개변수 규칙은 전역 함수만 사용할 수 있기 때문입니다). 코드는 다음과 같습니다.
function ThreadProc(스레드: TThread): 정수;
var
FreeThread: 부울;
시작하다
노력하다
Thread.Terminating이 아닌 경우
노력하다
스레드.실행;
제외하고
Thread.FFatalException := AcquireExceptionObject;
끝;
마지막으로
FreeThread := Thread.FFreeOnTerminate;
결과 := Thread.FReturnValue;
Thread.DoTerminate;
Thread.FFinished := 참;
SignalSync이벤트;
FreeThread이면 Thread.Free이고;
EndThread(결과);
끝;
끝;
코드가 많지는 않지만 실제로 스레드에서 실행되는 코드이기 때문에 전체 TThread에서 가장 중요한 부분이다. 다음은 코드를 한 줄씩 설명합니다.
먼저 스레드 클래스의 Terminating 플래그를 확인합니다. 종료됨으로 표시되지 않은 경우 스레드 클래스의 Execute 메서드를 호출하여 스레드 코드를 실행합니다. TThread는 추상 클래스이고 Execute 메서드는 기본적으로 추상 메서드입니다. 파생 클래스에서 실행 코드를 실행합니다.
따라서 Execute는 스레드 클래스의 스레드 함수로, 접근 충돌을 방지하는 등 Execute의 모든 코드를 스레드 코드로 간주해야 합니다.
Execute에서 예외가 발생하면 AcquireExceptionObject를 통해 예외 개체를 가져와 스레드 클래스의 FFatalException 멤버에 저장합니다.
마지막으로 스레드가 끝나기 전에 몇 가지 마무리 작업이 있습니다. 지역 변수 FreeThread는 스레드 클래스의 FreeOnTerminating 속성 설정을 기록한 다음 스레드 반환 값을 스레드 클래스의 반환 값 속성 값으로 설정합니다. 그런 다음 스레드 클래스의 DoTerminate 메서드를 실행합니다.
DoTerminate 메서드의 코드는 다음과 같습니다.
프로시저 TThread.DoTerminate;
시작하다
할당된 경우(FOnTerminate) 다음 동기화(CallOnTerminate);
끝;
매우 간단합니다. 동기화를 통해 CallOnTerminate 메서드를 호출하면 되며, CallOnTerminate 메서드의 코드는 다음과 같습니다. 간단히 OnTerminate 이벤트를 호출하는 것입니다.
절차 TThread.CallOnTerminate;
시작하다
할당된 경우(FOnTerminate) 그러면 FOnTerminate(Self);
끝;
OnTerminate 이벤트는 동기화에서 실행되기 때문에 본질적으로 스레드 코드가 아니라 기본 스레드 코드입니다(자세한 내용은 나중에 동기화 분석 참조).
OnTerminate를 실행한 후 스레드 클래스의 FFinished 플래그를 True로 설정합니다.
다음으로 SignalSyncEvent 프로세스가 실행되며 해당 코드는 다음과 같습니다.
절차 SignalSyncEvent;
시작하다
SetEvent(SyncEvent);
끝;
또한 매우 간단합니다. 전역 이벤트: SyncEvent를 설정하면 됩니다. 이 기사에서는 Event의 사용에 대해 나중에 자세히 설명하고 SyncEvent의 목적은 WaitFor 프로세스에서 설명합니다.
그런 다음 FreeThread에 저장된 FreeOnTerminate 설정을 기반으로 스레드 클래스를 해제할지 여부를 결정합니다. 스레드 클래스가 해제되면 몇 가지 작업이 있습니다. 자세한 내용은 다음을 참조하세요.
마지막으로 EndThread가 호출되어 스레드를 종료하고 스레드 반환 값이 반환됩니다.
이 시점에서 스레드는 완전히 끝났습니다.
(계속 예정)