2011-03-30 5 views
10

나는 두 개의 전역 변수 선언이 변수 만 메인 쓰레드로 작성Delphi는 단순한 유형의 스레드로부터 안전합니까?

var 
    gIsRunning: Boolean = False; 
    gLogCounter: Integer = 0; 

, 그리고 다른 스레드에서 읽어 보시기 바랍니다. 이 경우 이러한 변수는 스레드로부터 안전합니까?

+5

이 질문은 실제로 현재의 형태로 대답하는 것이 불가능합니다. 이에 대답하기 위해서는 threadsafe가 의미하는 것을 정확하게 지정해야합니다 (http://blogs.msdn.com/b/ericlippert/archive/2009/10/19/what-is-this-thing-you-call). -thread-safe.aspx). 당신은 내 변수가 찢어지기 쉽다는 것을 의미한다고 생각하는 대답을 가지고 있습니다. 자물쇠를 사용하여 두 변수에 원자 적으로 쓰는 방법을 다룬 답을 얻을 수 있습니다. 이 두 가지 해석 모두 정확할 수 있습니다. 하지만 충분한 정보가 없기 때문에 우리는 말할 수 없습니다. –

답변

46

아마도 원자 변수에 대해 말할 것입니다. 정수 및 부울 변수는 원자 적입니다. 부울 (바이트)은 항상 원자이며, 정수 (32 비트)는 컴파일러가 올바르게 정렬하기 때문에 원자입니다.

원자성이란 읽기 또는 쓰기 작업이 전체적으로 수행됨을 의미합니다. 스레드 A가 동일한 데이터의 원자 쓰기 및 스레드 B 원자 읽기를 동시에 실행하면 스레드 B가 읽는 데이터는 항상 일관 적입니다. 스레드 B가 읽는 비트는 현재 쓰기 작업에서 얻은 것이 불가능하며 이전 쓰레드의 일부 비트 (스레드 A)

원자 단위는 스레드 안전성을 의미하지 않습니다. 원자 변수를 사용하여 안전하지 않은 코드를 쉽게 작성할 수 있습니다. 변수 자체는 스레드 세이프가 될 수 없습니다. 전체 코드는 스레드 세이프 일 수 있습니다 (또는 아닐 수도 있습니다).

+6

베스트 요약 코멘트 나는 문제에 본! – Misha

+0

+1 "코드 전체가 스레드 세이프 일 수 있는지 여부 (스레드가 안전하지 않을 수도 있음)" –

+1

예. 컴파일러 나 CPU에 의한 코드 재정렬로 인해 원자 변수로 스레드 안전하지 않은 코드를 작성할 수 있습니다. 또는 CPU 또는 코어 간의 메모리 캐싱으로 인해 발생합니다. –

8

아니 그들이, 당신은 예를 들어 중요한 섹션을 사용하여 이러한 변수에 액세스해야합니다, 스레드 안전있는만큼이 할 수있는 하나 개의 스레드가있는 한 InitializeCriticalSection, EnterCriticalSectionLeaveCriticalSection 기능을

//declaration of your global variables 
var 
    MyCriticalSection: TRTLCriticalSection; 
    gIsRunning: Boolean; 
    gLogCounter: Integer; 

//before the threads starts 
InitializeCriticalSection(MyCriticalSection); 

//Now in your thread 
    EnterCriticalSection(MyCriticalSection); 
//Here you can make changes to your variables. 
    gIsRunning:=True; 
    inc(gLogCounter); 
//End of protected block 
LeaveCriticalSection(MyCriticalSection); 
+1

스레드 안전성이 부족한 이유는 무엇입니까? 해당 변수 중 하나에 잘못된 값이 수신 될 가능성이 있습니까? 독자가 잘못된 가치를 보게됩니까? –

+0

@Rob 내 대답은 당신의 또 다른 코멘트입니다. – RRUZ

+2

@Rob 잠금없이 잘못 될 수있는 것은 읽기 및 쓰기의 순서입니다. –

13

를 사용하지 않는 그들에게 글을 쓰고 그렇다면 쓰레드에 안전합니다. 스레드 안전성의 실제 문제는 두 개의 스레드가 동시에 값을 수정하려고 시도하는 것이므로 여기에는 해당 스레드가 없습니다.

레코드 나 배열과 같이 크기가 큰 경우 하나의 스레드가 값을 쓰고, 중간에 빠져 나가고, 컨텍스트 전환이 발생하고 다른 스레드가 부분적 (따라서 손상된) 데이터를 읽는 데 문제가있을 수 있습니다. 그러나 개별 부울 (1 바이트) 및 정수 (4 바이트) 값의 경우 컴파일러는 CPU가 이들에 대한 모든 읽기 및 쓰기가 원자 적이라는 것을 보장 할 수있는 방식으로 자동으로 정렬 할 수 있으므로 여기서 문제가되지 않습니다.

+0

@Mason, ok 당신이 대답하는 것에 동의하지만, 읽은 경우에도 배수 스레드의 전역 변수에 액세스하는 것은 좋은 설계 방법으로 간주 될 수 있습니다. – RRUZ

+1

@RRUZ : 고려하고있는 사람에 따라 다릅니다. "어디서나 좋은 디자인 관행에 대한 보편적 목표 가이드"를 보지 못했습니까? 그래서 객관적으로 대답 할 수 있기 때문에 기술적 인 대답을했습니다. –

+2

@Rruz가 없으므로 전역 변수는 좋은 설계 방법으로 간주되지 않습니다. 스레드는 그와 아무 관련이 없습니다. –

11

단순 유형은 메모리에서 단일 읽기 (또는 단일 쓰기로)로 읽을 수있는 한 "스레드 안전"입니다. CPU 메모리 버스 너비 또는 "정수"크기 (32 비트 대 64 비트 CPU)로 정의되어 있는지 확실하지 않습니다. 어쩌면 다른 사람이 그 부분을 분명히 할 수 있습니다.

저는 현재 읽기 크기가 적어도 32 비트임을 압니다. (인텔의 경우 286 일 전까지 한 번에 8 비트 밖에 없었습니다.)

이 점에 대해 알아야 할 것이 하나 있습니다. 한 번에 32 비트를 읽을 수는 있지만 모든 주소에서 읽기를 시작할 수는 없습니다. 32 비트 (또는 4 바이트)의 배수 여야합니다. 따라서 정수가 32 비트에 정렬되지 않은 경우 두 개의 연속 된 읽기에서 정수를 읽을 수 있습니다. 고맙게도 컴파일러는 모든 필드를 32 비트 (또는 64 비트)로 자동 정렬합니다.

그러나 예외적으로 압축 된 레코드는 정렬되지 않으므로 이러한 레코드의 정수는 스레드로부터 안전하지 않습니다.

크기 때문에 int64는 스레드로부터 안전하지 않습니다. 대부분의 부동 유형에 대해 동일한 내용을 전달할 수 있습니다. (단 하나를 제외하고 나는 믿는다).

이제 모든 것을 염두에두고 실제로 여러 스레드에서 전역 변수를 작성하고 여전히 "스레드로부터 안전"할 수있는 몇 가지 상황이 있습니다.예를 들어

, 여기

var 
    LastGoodValueTested : Integer 

procedure TestValue(aiValue : Integer); 
begin 
    if ValueGood(aiValue) then 
    LastGoodValue := aiValue 
end; 

, 당신은 여러 스레드에서 루틴 TestValue를 호출 할 수 있고 당신은 손상 LastGoodValueTested 변수하지 않을 것입니다. 변수에 쓰여지는 값은 아주, 마지막이 아닐 수도 있습니다. (스레드 컨텍스트 스위치가 ValueGood (aiValue)와 할당 사이에서 발생하는 경우). 따라서 필요에 따라 허용 될 수도 있고 허용되지 않을 수도 있습니다. 이제

, 여기

var  
    gLogCounter: Integer = 0; 

procedure Log(S : string); 
begin 
    gLogCounter := gLogCounter + 1; 
end; 

을 수행 할 수 있습니다 실제로 손상 카운터는 단항 연산 아니기 때문에. 먼저 변수를 읽습니다. 그런 다음 1을 더하십시오. 그런 다음 다시 저장하십시오. 스레드 컨텍스트 전환은 이러한 작업 중에 발생할 수 있습니다. 따라서 동기화가 필요한 경우입니다. 이 경우

, 그것은

procedure Log(S : string); 
begin 
    InterlockedIncrement(gLogCounter); 
end; 

나는이 중요한 부분을 사용하는 것보다 약간 빠른 생각에 다시 ...하지만 잘 모르겠어요 수 있습니다.

+0

+1 지금까지 최고의 설명. 바이트/정수에 직접 할당하는 것은 스레드로부터 안전합니다. * 임의의 산술 및/또는 논리를 수행하려면 동기화가 필요합니다. –

+0

아니요, @Lieven, 산술을하는 것조차 하나의 스레드 만이 수행하는 한 OK입니다. 다른 모든 스레드가 독자 인 경우 걱정할 것이 없습니다. 판독기 스레드는 변수의 이전 값 또는 새 값을 읽습니다. "중간"가치를 읽을 기회가 없습니다. –

+0

@Rob, @Ken에 의해 주어진 예제에서와 같이 여러 스레드가 관련되었을 때를 암시합니다. 명시 적으로 * 만드는 것이 더 좋았을 것입니다 (귀하의 의견은 이제 : *) * –

관련 문제