이 MSDN 문서는 멀티 스레드 응용 프로그램 개발의 첫 번째 단계에 불과합니다. 즉, 읽기/쓰기가 가능한 데이터가 확실하지 않기 때문에 "공유 변수를 잠금으로 보호합니다 (중요한 섹션이라고도 함)를 의미합니다. 모든 스레드에 대해 동일 "합니다.
CPU 당 코어 캐시는 잘못된 값을 읽게되는 가능한 문제 중 하나 일뿐입니다.경쟁 조건으로 이어질 수있는 또 다른 문제는 리소스에 동시에 쓰는 두 개의 스레드입니다. 이후에 저장할 값을 아는 것은 불가능합니다.
코드는 데이터가 일관성이 있기를 기대하기 때문에 일부 멀티 스레드 프로그램은 잘못 작동 할 수 있습니다. 멀티 스레딩을 사용하면 작성한 코드가 개별 명령어를 통해 공유 변수를 처리 할 때 예상대로 실행되는지 확신 할 수 없습니다.
InterlockedExchange/InterlockedIncrement
함수는 실제로 모든 CPU 코어에 대한 캐시 일관성을 강제하고, 따라서 ASM 동작 코드의 실행 스레드 안전 할 것이다 저수준의 ASM 로크 프리픽스 오피 코드 (또는 XCHG EDX,[EAX]
오피 같이 설계된 잠긴) . 예를 들어
, 여기 당신이 문자열 값을 할당 할 때 문자열 참조 횟수가 구현되는 방법이다 (System.pas에 _LStrAsg
참조 -이 our optimized version of the RTL for Delphi 7/2002에서입니다 - 델파이 원래의 코드가 저작권 때문에) :
MOV ECX,[EDX-skew].StrRec.refCnt
INC ECX { thread-unsafe increment ECX = reference count }
JG @@1 { ECX=-1 -> literal string -> jump not taken }
.....
@@1: LOCK INC [EDX-skew].StrRec.refCnt { ATOMIC increment of reference count }
MOV ECX,[EAX]
...
첫 번째 INC ECX
과 LOCK INC [EDX-skew].StrRec.refCnt
사이에는 차이가 있습니다. 첫 번째 증분 ECX 및 참조 횟수 변수가 아니지만 첫 번째는 스레드로부터 안전하지 않지만 두 번째 접두사는 LOCK로 시작되므로 스레드로부터 안전합니다.
그러나이 LOCK 접두어는 multi-thread scaling in the RTL의 문제 중 하나입니다. 최신 CPU에서는 더 좋지만 여전히 완벽하지는 않습니다. 따라서 응용 프로그램이 더 나은 규모 및 전체 전력의 사용을 만들 것입니다,
var GlobalVariable: string;
GlobalSection: TRTLCriticalSection;
procedure TThreadOne.Execute;
var LocalVariable: string;
begin
...
EnterCriticalSection(GlobalSection);
LocalVariable := GlobalVariable+'a'; { modify GlobalVariable }
GlobalVariable := LocalVariable;
LeaveCriticalSection(GlobalSection);
....
end;
procedure TThreadTwp.Execute;
var LocalVariable: string;
begin
...
EnterCriticalSection(GlobalSection);
LocalVariable := GlobalVariable; { thread-safe read GlobalVariable }
LeaveCriticalSection(GlobalSection);
....
end;
지역 변수를 사용하여 임계 영역을 짧게한다 :
그래서 중요한 섹션을 사용하여 스레드 안전 코드를 만드는 가장 쉬운 방법입니다 귀하의 CPU 코어 중. EnterCriticalSection
과 LeaveCriticalSection
사이에는 하나의 스레드 만 실행됩니다. 다른 스레드는 EnterCriticalSection
호출에서 대기합니다 ... 따라서 중요한 섹션이 짧을수록 응용 프로그램이 빠릅니다. 일부 잘못 설계된 멀티 스레드 응용 프로그램은 실제로 모노 스레드 응용 프로그램보다 느려질 수 있습니다!
중요한 섹션 내의 코드가 예외를 발생시킬 수있는 경우 잠금 해제를 보호하고 응용 프로그램의 deadlock을 방지하려면 항상 try ... finally LeaveCriticalSection() end;
블록을 작성해야합니다.
공유 데이터를 잠금 (예 : 중요 섹션)으로 보호하면 델파이는 스레드로부터 안전합니다. RTL 함수 내부에 LOCK이 있어도 참조 카운팅 된 변수 (문자열 등)조차도 보호해야한다는 점에 유의하십시오.이 LOCK은 올바른 참조 카운팅을 가정하고 메모리 누수를 피할 수 있지만 스레드로부터 안전하지는 않습니다 . 되도록 빨리하기 위해 see this SO question.
InterlockExchange
및 InterlockCompareExchange
의 목적은 공유 포인터 변수 값을 변경하는 것입니다. 포인터 값에 액세스하기 위해 임계 섹션의 "경량"버전으로 볼 수 있습니다.
모든 경우에 멀티 스레드 코드를 작성하는 것은 쉽지 않습니다. 심지어 , as a Delphi expert just wrote in his blog입니다.
공유 데이터가없는 간단한 스레드를 작성하거나 (스레드가 시작되기 전에 데이터의 개인 복사본을 만들거나 읽기 전용 공유 데이터를 사용합니다 (에센스별로 스레드 안전) 또는 일부를 잘 호출해야합니다 설계되고 검증 된 라이브러리 - 예 : http://otl.17slon.com -을 사용하면 많은 디버깅 시간을 절약 할 수 있습니다.
David, 부울, 정수 등의 참조는 원자 적으로 (적절하게 정렬 된 경우) 따라서 스레드로부터 안전하다고 종종 언급됩니다. 나는 받아 들여진 응답이 여기에서 생각한다 http://stackoverflow.com/questions/510031/list-of-delphi-data-types-with-atomic-read-write-operations 관점에서 이것을 둔다. "읽기는 스레드로부터 안전합니다. 쓰기는 스레드로부터 안전하지 않습니다." –
@LU RD 정확해야합니다. threadsafe는 무엇을 의미합니까? 그리고 정확하게 "읽기는 스레드로부터 안전합니다, 쓰기는 스레드로부터 안전하지 않습니다"라는 의미입니까? –
모든 스레드 컨텍스트에서 사용하기에 안전한 원자 변수에 대한 작업을 고려할 수 있다고 가정하지 않는 것은 일반적인 경고 일뿐입니다. –