8.1 원자 액세스 : 가족 연동 기능
윈도우는 선제 시스템이지만 때로는 수요가 너무 출력은 변수에 동일한 스레드 기능을 동시에 작동 할 수 있지만 코드를 작성 작업에 동일한 결과와 뜻을 보장 할 수는 없지만.
그들은 결과 코드를 해당 보장 할 수 있습니다, 올바르게 사용한다면 윈도우는 기능을 제공합니다. 이 원자 작업입니다 :
LONG InterLockedExchangeAdd (
PLONG plAddend,
LONG lIncrement);
이 기능을 사용하면 전역 변수 캔 프레스 순차적으로 증가를 보장합니다. 이 방법의 모든 스레드가 공유 변수를 조작 할 수 있어야, 어떤 스레드는 단순히 공유 변수를 수정하려면 C 문을 호출하지 않아야합니다.
구현 함수 종류 x86 플랫폼의 CPU 대 CPU 플랫폼은 연동 기능은 다른 액세스 CPU 동일한 메모리 어드레스를 방지하기 위해, 하드웨어 버스 신호를 발행 될지에 의존한다. 알파 플랫폼에서이다 :
- 는 CPU에 특별한 플래그를 열고 메모리 주소를 나타 내기 액세스 할 수
- 값은 메모리 판독 레지스터
- 이 레지스터를 수정
- CPU의 플래그가 오프 인 경우, 두 번째 단계로 옮겼다. 특정 플래그가있는 경우, 그렇지 않으면,이 값은 메모리에 다시 재기록이다.
실행 시간의 네 번째 단계는, 특별한 플래그 비트 CPU가 얼마나 가까이? 사실, 한 동일한 메모리 주소를 수정하는 또 다른 CPU의 시도가, 함수가 반환에게 두 번째 단계를 연동의 결과로, CPU의 특별한 플래그를 해제 할 수 있습니다.
기능 클럭 사이클을 연동하는 것은 (일반적으로 이하 50 이상) 몇 CPU 사이클이 매우 짧고, 커널 모드 (일반적으로 1000 클럭 사이클)에 사용자 모드로 전환하지 않습니다.
InterLockedExchangeAdd 원래 값으로 사용될 수있는 값 (만큼 음의 값을 매개 변수 두번째)를 감산. 여기서 두 연동 기능은 :
긴 (InterlockedExchange
PLONG plTarget,
긴 좌변
) // 교환기 (32) 및 (64)는 두 개의 32 비트 값
VOID InterLockedExchangePointer (
PVOID ppvTarget,
PVOID pvValue
) // 64 개의 64 비트 값 교환기 (32)와 동일한
특히 조심, 잠금주기는 CPU의 시간을 낭비합니다. 또한 루프 변수 잠금 잠금 및 데이터 보호 캐시의 상이한 사이클에서 유지되어야한다. 같은 캐시 경우, CPU 리소스의 사용은 캐시를 사용하여 리소스에 액세스하는 모든 CPU의 시도와 경쟁 할 것이다.
당신은 컴퓨터의 단일 CPU 사이클 잠금을 사용하지 않아야합니다. 스레드 루프가 실행중인 경우,이 전 CPU의 낭비가 될 것이다, 이것은이 값을 수정하는 또 다른 스레드를 방지합니다.
잠금 루프는 항상 짧은 시간 보호 된 리소스에 액세스하는 것을 가정한다. 이로써주기 다음 커널 모드로 커널 모드로 전환보다 효율적으로 실행하고 할 수와 대기 상태가됩니다. 자원이 다른 스레드에 대한 액세스를 거부하는 경우, 자원 때까지 커널 모드로 스레드되지 않도록에 소비하는 CPU 시간이 작업을 수행 할 수 있습니다.
잠금 루프는 매우 일반적이지만, CPU 시간의 낭비가 될 것입니다, 너무 오래 스레드 루프를 실행하도록 허용해서는 안주의하십시오. 추가의 두 개의 연동 기능 :
PVOID InterLockedCompareExchange ( PLONG plDestination; LONG lExchange; LONG lComparand ); // 32 64 = 32 PVOID InterLockedCompareExchangePointer ( PVOID * ppvDestination, PVOID pvExchange, PVOID pvComparand ); // 32 = 32 64 = 64
LONG InterLockedIncrement (PLONG plAddend);
LONG InterLockedDecrement ( PLONG plAddend );
8.2 캐시 라인
우리는 캐시가 다중 프로세서 컴퓨터에서 생성 할 수 있습니다 고성능 애플리케이션을위한 전제 조건 알고있다. CPU가 메모리에서 바이트를 가져 오는 경우, 캐시를 채우기에 충분한 바이트를 한 바이트에서 촬영하지만 제거되지 않습니다. 캐시 (32) 또는 64 바이트 (는 CPU 별에 따라) 항상 32 바이트 또는 64 바이트 경계에 정렬된다. 캐시 라인은 CPU의 운영 효율성을 향상시키기 위해 설계되었습니다.
그러나, 다중 프로세서 환경에서 캐시 라인 메모리 업데이트는 예를 들어, 더 어렵게 만든다 :
- CPU1는 바이트를 읽고, 바이트는 자신의 이웃 바이트 읽기 CPU1 캐시 라인입니다 수 있습니다.
- 각 바이트는 동일한 캐시 라인에 판독 CPU2되도록 CPU2는 첫 번째 단계 바이트를 판독한다.
- 캐시 라인이 CPU1 바이트에 기록되도록, 메모리에 바이트를 수정 CPU1. 그러나 정보는 RAM에 기록되지 않았습니다.
- CPU2는 다시 같은 바이트를 읽습니다. 바이트는 메모리에 액세스 할 수는 없지만, CPU2 바이트 적은 메모리의 새로운 값을 볼 수 있도록, CPU2 캐시 라인을 넣어 되었기 때문에.
이러한 상황은 심각한 결과를 초래할 것입니다. 물론, 칩 설계자는이 제목을 잘 알고 있으며,이 문제를 해결하기 위해 자신의 칩을 설계합니다. 수정 된 바이트의 CPU 캐시 라인이, 다른 CPU 컴퓨터가 상황을 알려드립니다 특히, 자신의 캐시 라인은 무효가됩니다. 그러므로, CPU1에 CPU2 바이트 값으로 캐시가 무효가 변성. 4 단계에서, CPU1는 데이터 캐시 라인을 다시 채우기 위해 다시 메모리, CPU2의 필수 액세스 메모리로 빠르게 캐시 라인 메모리를 삽입해야합니다. 따라서, 캐시 라인은 속도뿐만 아니라 다중 프로세서 컴퓨터에 부정적인 요인을 향상시킬 수 있습니다.
당신이 결합, 경계 데이터 라인 메모리 블록 캐시 라인 응용 프로그램을 캐시해야이 수단의 모든. 다른 메모리 주소를 분리 적어도 캐시 라인 경계에서 서로 다른 CPU가 액세스 할 수 있는지 확인 할 때 그렇게하는 목적. 읽기 전용 데이터를 별도의 읽기 및 쓰기 데이터의 액세스 시간과 함께 데이터가 있어야합니다.
8.3 고급 스레드 동기화
가족 연동 기능은 단일 원자 값의 동작의 동작에 유용 할 때. 그러나, 대부분의 경우에, 원자 조작 더 복잡한 데이터 구조를 조작하기 위해서는, 32 개 또는 64 이상의 데이터 구조의 하나의 값보다 더 복잡한 처리되는, 연동 기능 할 수없는 용도, 및 윈도우의 다른 기능을 사용한다.
스레드가 액세스 자원을 공유, 또는 "특별 이벤트"에 대한 통지를하고자 할 때, 스레드가 기다리고 있는지 설명하기 위해, 일부 매개 변수를 나타냅니다, 운영 체제 함수를 호출해야합니다. 운영 체제 발견 리소스를 사용할 수, 또는 특별한 이벤트가 발생하면, 함수가 반환, 스레드를 유지하면서하는 상태를 예약 할 수 있습니다.
리소스를 사용할 수없는, 또는 특별한 이벤트가 발생하지 않은 경우, 시스템이 대기 상태에있는 스레드를 만들 것입니다, 스레드는 예약 할 수 없습니다. 이 스레드 폐기물 CPU 시간을 방지 할 수 있습니다. 스레드가 대기 상태에있을 때, 스레드 대신 에이전트와 같은 시스템은 작업을 수행 할 수 있습니다. 운영 체제는 리소스를 사용할 수 후에는 자동으로 스레드가 종료 대기 상태를 만들 것입니다, 스레드에 필요한 기억할 수있는 때 스레드 동기화 및 특별 이벤트.
실제 상황으로, 스레드의 대부분은 거의 항상 대기 상태입니다. 시스템이 모든 스레드가 대기 몇 분에 발견하면, 시스템 강력한 관리 기능 역할을 담당 할 것입니다.
거기에는 동기화 개체 없으며, 운영 체제가 특별 행사의 다양한 찾을 수없는 경우, 스레드는 다음을 소개하는 방법을 사용하고, 속도를 유지하기 위해 자신의 특별한 이벤트를해야 할 것이다. 운영 체제가 내장되어 있기 때문에, 기능은 스레드 동기화를위한 지원, 행이이 방법을 사용하지 않습니다.
이 방법을 사용하여 국가가, 스레드가 자신의 성격과 다른 스레드 동기화를 완료 할 수 있습니다, 여러 스레드가 공유하거나 액세스 변수 수있는 쿼리를 계속하는 것입니다.
8.4 임계 영역
중요한 코드 섹션은 코드가 일부 공유 자원에 대한 배타적 접근을 보장하기 위해 실행하기 전에 코드의 조각을 만드는 데 사용됩니다. 코드 라인의 숫자를 얻기 위해 자원의 방법을 사용하는 "원자 작업"이 될 수 있습니다.
지정 데이터 구조 CRITICAL_SECTION의 g_cs 후 모든 공유 리소스와 접촉하기 위해 패키지와 코드의 LeaveCriticalSection EnterCriticalSection 함수를 호출합니다. g_cs 주소를 전달하는 EnterCriticalSection과 LeaveCriticalSection 호출에 다음 사항을 유의하십시오.
연동 기능은 동기화 문제를 해결하는 데 사용할 수없는 경우, 당신은 키 코드를 시도해야합니다. 그것의 장점은 매우 쉽게 사용할 수 있나요 내부적으로 사용되는 연동 기능이 너무 빨리 실행할 수 있다는 것입니다.
키 코드의 8.4.1 정확한 설명
키 코드가 유효한 이유를 알고 싶다면, 먼저 CRITICAL_SECTION에서이 데이터 구조를 도입했다. 마이크로 소프트가 전체 이야기 알 필요를 본 적이 있기 때문에, 완전한 설명 PlatformSDK 문서가 아닙니다. 어쩌면 당신은 회원을 운영 할 수있는 수단을 가지고 있지만 결코 그렇게해야한다.
이 구조를 사용하여, 당신은 그것을 구조의 주소를 전달 윈도우 함수를 호출 할 수 있습니다.
일반적 CRITICAL_SECTION은 변수를 쉽게 할 수 있고, 이러한 구조를 참조하도록하는 프로세스의 모든 스레드가있어서 그래서, 전역 변수로 할당 될 수있다. 지역 변수 할당 또는 동적으로 힙에 할당으로 물론, 그것은 또한 사용할 수 있습니다. 그것은 단지 두 가지 요구 사항이 있습니다 :
- 모든 스레드가 자원이 자원 CRITICAL_SECTION 구조를 보호 할 책임 주소를 알고 있어야 액세스 할 수 있습니다.
- CRITICAL_SECTION 구조체 멤버 전에 초기화는 스레드에서 보호 된 자원에 액세스를 시도해야한다.
VOID InitializeCriticalSection (PCRITICAL_SECTION의 PCS);
이 기능을 사용하면 EnterCriticalSection을 호출하기 전에 사용할 수 있습니다, 더 리턴 값이 없다, 실패하지 않습니다.
스레드가 더 이상 리소스에 액세스 분명 CRITICAL_SECTION에 기여 시도의 인식 동시에,이다 :
VOID 된 DeleteCriticalSection (PCRITICAL_SECTION의 PCS);
키 코드를 사용 후 어떤 스레드, 삭제하지 않습니다.
EnterCriticalSection보기 기능이 자원에 액세스하는 전류 가변, 그것은 다음의 시험을 수행되었음을 나타내는 멤버 변수 구조 책임이있다 :
- 어떤 스레드가 자원에 액세스하지하는 경우, EnterCriticalSection을 호출 스레드가 즉시 액세스 및 반환을받은 것을 나타 내기 위해 멤버 변수를 업데이트합니다 스레드 실행을 계속할 수 있습니다.
- 멤버 변수가 표시된 경우, 호출 스레드가 자원에 대한 액세스 권한이 부여 된 후 EnterCriticalSection을 호출 스레드가 스레드가 계속 실행할 수 있도록하는 것이, 방문 즉시 반환 할 수있는 권리를 부여 횟수를 표시하기 위해 이러한 변수를 업데이트합니다.
- 멤버 변수는 호출 스레드 이외에 스레드가 자원에 대한 액세스 권한이 부여되었음을 나타냅니다 경우, EnterCriticalSection은 CRITICAL_SECTION 멤버 변수를 업데이트합니다. 현재 스레드의 리소스에 대한 액세스가 LeaveCriticalSection 함수를 호출하면 상태에서 예약 할 수있는 스레드를 변경합니다.
내부적으로 EnterCriticalSection 함수는 매우 복잡하지 않습니다, 그것은 단지 몇 가지 간단한 테스트에서 수행합니다. 이것은 원자 할 모든 테스트를 수행 할 수 있기 때문에 그래서 효과적이다. 하더라도 여전히 제대로 작동 할 수 정확히 같은 시간 EnterCriticalSection 함수에서 다중 프로세서 컴퓨터 전화에 두 개의 스레드, 리소스에 대한 액세스, 대기 상태로 스레드를 얻을 수있는 스레드.
EnterCriticalSection 그럼에도 불구하고 쉽게 사용할뿐만 아니라, 위험,가 대기 상태에있는 스레드를 직접 것, 스레드에도 결코 다시 작성하는 나쁜 프로그램에서 오랜 시간을 스케줄 할 수없는 것 그것은 CPU 시간이 주어졌다. 이 스레드는 욕망 스레드 CPU 시간이되었다.
스레드는 EnterCriticalSection이 보류되는 경우, 스레드는이 상황이 갈망 스레드 CPU 시간이라고, 오랜 기간 동안 예정되지 않습니다.
그러나 실제로는,이 스레드는 갈증 CPU 시간하는 EnterCriticalSection을 호출 비정상적인 생산하지 않습니다 않습니다 및 시간 제한 시간, 디버거가 데이터 결정에 의해 다음 레지스트리 경로 CriticalSectionTimeout에 포함되는 시간에 전달 될 수 있습니다.
HKEY_LOCAL_MACHINE \ 시스템 \ CURRENTCONTROLSET \ 컨트롤 \ 과장
이 값은 기본 30 일에 대해, 2592000s이다, 초 단위입니다. 이 값은 다른 악영향 더 3S 스레드가 아닌 시스템의 중요한 코드 세그먼트에 대한 일반 대기에 영향을 너무 작은 수 없습니다.
EnterCriticalSection 다른 함수는 TryEnterCriticalSection, 그것은 대기 상태로 스레드가, 반대로, 반환 값은 리소스에 액세스 할 수있는 호출 스레드를 표시 할 수 허용하지 않습니다. 이 발견이 경우, 그렇지 않으면 TRUE를 반환, 자원 및 반환 FALSE를 차지한다. 이 TRUE, 다음 유사한 효과 EnterCriticalSection을 반환하는 경우, 리소스에 액세스 LeaveCriticalSection 종료 후 동일한 횟수를 호출하는 스레드를 시작합니다.
VOID LeaveCriticalSection (PCRITICAL_SECTION의 PCS);
차체 구조 부재 변수의 관점에서이 함수 파라미터는 각 카운트가 1만큼 감소되는 변수 호출 스레드가 공유 된 AD에 대한 액세스를 지정된 횟수 나타내는. 카운트가 0보다 큰 경우, 함수는 직접 반환합니다.
추천 EnterCriticalSection 및 LeaveCriticalSection이 시험을 수행 원자 조작에 갱신 될 수있다. 그러나, LeaveCriticalSection는 항상 즉시 반환, 대기 상태로 실을 수 있습니다.
8.4.2 중요한 코드 세그먼트와 사이클 잠금
스레드가 다른 스레드가 소유 한 중요한 부분을 입력하려고 할 때, 호출 스레드는 즉시 보류됩니다. 스레드가 꽤 고가 인 사용자 모드 (1000 개를 초과 CPU 싸이클)로부터 커널 모드로 전송되어야 함이 방법. 현재 스레드가 자원이 멀티 프로세서 컴퓨터 및 자원에 곧 양도합니다 제어에 서로 다른 프로세서에서 실행할 수 있습니다 소유하고 있습니다.
당신이 자원에 대한 통제의 가자 커널 상태로 다른 스레드 전에 스레드의 자원이 경우, CPU 시간을 많이 낭비됩니다.
중요한 코드 세그먼트의 성능을 향상시키기 위해, 마이크로 소프트는 사이클 잠금 코드 세그먼트에 참여합니다. 따라서, EnterCriticalSection가 호출 될 때, 그것은 반복적으로 자원을 얻기 위해 노력 잠금 루프 순환을 사용합니다. 만 커널 모드로 스레드마다 실패했습니다.
당신이 중요한 코드 세그먼트 루프를 잠 그려면, 다음과 같은 기능 키 코드 섹션을 초기화 호출해야합니다 :
BOOL InitialCriticalSectionAndSpinCount (
PCRITICAL_SECTION PC와,
DWORD dwSpinCount);
그리고 유사한 초기화 중요한 부분은,이 기능은 또한 CRITICAL_SECTION 주소가 필요합니다. 액세스 자원을 시도 할 때 두 번째 인수는, 당신은 루프 반복 할 횟수는 0을 0xFFFFFFFF 사이의 숫자가 될 수 있습니다. 이 기능을 하나의 컴퓨터 프로세서에 호출되면, 두 번째 매개 변수는 항상 제로 카운트 설정, 무시됩니다.
또한 사이클 잠금 루프의 수를 변경할 수 있습니다 :
DWORD SetCriticalSectionSpinCount (
PCRITICAL_SECTION PC와,
DWORD dwSpinCount
);
8.4.3 중요한 코드 세그먼트 에러 핸들링
InitialCriticalSection 기능은 마이크로 소프트가 디자인 그것의 시작 부분에 이것에 대해 생각하지 않은, 그리고 반환 값은 무효로 설정되어 있으므로 것을 최소화 할 수있다 실패합니다. 함수가 실패하면, 시스템은 할당 된 메모리 블록에 따른 디버깅 정보를 얻을 수있다. 메모리 할당이 실패 할 경우, 특별한 상황의 STATUS_NO_MEMORY이있을 것이다. 예외의 트랙을 유지하기 위해 구조화 된 예외 처리를 사용합니다.
메모리 할당 에러의 블록은,이 FALSE를 반환하는 경우 갱신 InitialCriticalSectionAndSpinCount 함수를 사용하여보다 용이하게 문제를 추적 할 수있다.
또 다른 문제가 있습니다. 코드의 하나 개 이상의 스레드를 동시에 경합 중요한 부분 경우, 중요한 코드 세그먼트는 한 번에 커널 객체를 생성합니다. 경합의 드문 경우로, 그래서 시스템은 대부분의 코드 때문에 결코 경쟁에서, 시스템 자원을 많이 절약 할 수 있습니다 초기 요구, 전 세계의 커널 객체를 생성하지 않습니다.
메모리 부족, 키 코드가 경합 때, 시스템은 EnterCriticalSection은 EXCEPTION_INVALID_HANDLE 예외를 생성합니다, 이벤트 커널 개체를 만들 수 없습니다. 이 오류가보고 너무 작기 때문에, 일반적으로 특별한 치료를하지 않는 실수를 간과하는 것은 매우 쉬운 것입니다.
일부 메모리를 사용할 수있게하기위한 따라서 오류가 발생하는 구조적 예외 오류의 사용을 추적하는 두 가지 방법이 있습니다, 당신은 단지 보호 된 자원을 코드의 임계 영역에 액세스 할 수 없습니다, 당신은 다시 EnterCriticalSection을 호출 기다릴 수 있습니다.
또는 중요한 코드 세그먼트를 생성 InitialCriticalSectionAndSpinCount 스위치 정보 dwSpinCount 파라미터의 상위 비트를 설정. 고통은 정보가이 이벤트 커널 오브젝트를 생성 할 때 높은를 설정하고 초기화하는 동안 임계 영역과 연관되어 있습니다. 이벤트가 생성 될 수없는 경우, 함수 반환 FALSE는 더 제대로 할 오류를 처리 할 수 있습니다.
8.4.4 매우 유용한 팁과 트릭
- 변수를 사용하여 리소스를 CRITICAL_SECTION.
- 동시에 여러 리소스에 액세스하지 마십시오 경향이 교착 상태입니다. 오랫동안 키 코드를 실행하지 마십시오.
- 예를 들어, 스레드 프로세스 sendMessage 첨부하는 경우, 걸리는 시간을 아는 것은 불가능하다. 다른 스레드가 액세스 할 수있는이 시간 동안 시간은 몇 년이 될 수 밀리 초 수 있습니다.
- 주요 부분에 사용 된 주요 부분, 예를 들면, 만 공유 데이터 보호 조작부 전부는 아니지만.