뮤텍스와 크리티컬 섹션의 차이점은 무엇입니까?


134

리눅스, Windows 관점에서 설명해주세요.

나는 C #으로 프로그래밍하고 있는데,이 두 용어가 차이를 만들 것입니다. 예를 들어 가능한 한 많이 게시하십시오 ....

감사

답변:


232

Windows의 경우 중요 섹션은 뮤텍스보다 가볍습니다.

뮤텍스는 프로세스간에 공유 될 수 있지만 항상 약간의 오버 헤드가있는 커널에 대한 시스템 호출이 발생합니다.

중요 섹션은 하나의 프로세스 내에서만 사용할 수 있지만 경합의 경우 커널 모드로만 전환 할 수 있다는 장점이 있습니다. 일반적이지 않은 비경쟁 획득은 매우 빠릅니다. 경합의 경우, 이벤트 또는 세마포어와 같은 일부 동기화 기본 요소를 대기하기 위해 커널로 들어갑니다.

나는 둘 사이의 시간을 비교하는 빠른 샘플 앱을 작성했습니다. 1,000,000 개의 비경쟁 획득 및 릴리스 시스템에서 뮤텍스가 1 초 이상 걸립니다. 1,000,000 획득에 중요한 섹션은 ~ 50 ms가 소요됩니다.

테스트 코드는 다음과 같습니다. mutex가 첫 번째 또는 두 번째 인 경우 비슷한 결과를 얻었으므로 다른 효과는 보이지 않습니다.

HANDLE mutex = CreateMutex(NULL, FALSE, NULL);
CRITICAL_SECTION critSec;
InitializeCriticalSection(&critSec);

LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
LARGE_INTEGER start, end;

// Force code into memory, so we don't see any effects of paging.
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    EnterCriticalSection(&critSec);
    LeaveCriticalSection(&critSec);
}

QueryPerformanceCounter(&end);

int totalTimeCS = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);

// Force code into memory, so we don't see any effects of paging.
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);

QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    WaitForSingleObject(mutex, INFINITE);
    ReleaseMutex(mutex);
}

QueryPerformanceCounter(&end);

int totalTime = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);

printf("Mutex: %d CritSec: %d\n", totalTime, totalTimeCS);

1
이것이 관련되어 있는지 여부는 확실하지 않지만 (코드를 컴파일하고 시도하지 않았기 때문에) INFINITE로 WaitForSingleObject를 호출하면 성능이 저하되는 것을 발견했습니다. 타임 아웃 값 1을 전달한 다음 반환을 확인하면서 루핑하면 일부 코드의 성능이 크게 달라집니다. 이것은 대부분 외부 프로세스 핸들을 기다리는 맥락에서 뮤텍스가 아닙니다. YMMV. 뮤텍스가 그 수정으로 어떻게 작동하는지 보는 데 관심이 있습니다. 이 테스트와 시간 차이가 예상보다 커 보입니다.
— 트로이 하워드

5
@TroyHoward는 기본적으로 그 시점에서 스핀 잠금이 아닙니까?
— dss539

이 구별의 이유는 아마도 주로 역사적입니다. 비 경쟁적 경우 (CriticalSection)만큼 빠른 (핵심 명령이 거의없고 syscall이없는) 잠금 기능을 구현하는 것은 어렵지 않지만 프로세스간에 (공유 메모리 조각으로) 작동합니다. 예를 들어 Linux futexes .
— regnarg

2
@TroyHoward는 CPU를 항상 100 %로 실행하고 INFINITE가 더 잘 작동하는지 확인하십시오. 전원 전략은 내 컴퓨터 (Dell XPS-8700)에서 속도를 늦추기로 결정한 후 최대 속도로 다시 크롤링하는 데 최대 40ms가 걸릴 수 있습니다. 이는 절전 모드 나 밀리 초 동안 대기하는 경우에는 수행되지 않을 수 있습니다.
— Stevens Miller

여기서 시연되고있는 것이 무엇인지 잘 모르겠습니다. 일반적으로 중요 섹션에 들어가려면 어떤 종류의 세마포어를 확보해야합니다. 무대 뒤에서 O / S는 뮤텍스를 요구하지 않고이 중요한 섹션 동작을 효율적으로 구현할 수있는 방법을 가지고 있습니까?
— SN

89

이론적 인 관점에서 중요한 섹션 은 코드가 공유 리소스에 액세스하기 때문에 여러 스레드에서 한 번에 실행하면 안되는 코드입니다.

뮤텍스 임계 영역을 보호하기 위해 사용된다 (때로는 데이터 구조의 이름) 알고리즘이다.

세마포어 및 모니터 는 뮤텍스의 일반적인 구현입니다.

실제로 Windows에서 사용 가능한 많은 뮤텍스 구현이 있습니다. 잠금 수준, 범위, 비용 및 다양한 수준의 경합에서 성능에 따라 구현 결과에 따라 다릅니다. 다양한 뮤텍스 구현 비용에 대한 차트는 확장 성 을 위해 CLR 인사이드 아웃-동시성 사용을 참조하십시오 .

사용 가능한 동기화 기본 요소.

lock(object)문은 사용하여 구현됩니다 Monitor- 참조 MSDN을 참조.

지난 몇 년 동안 비 차단 동기화 에 대한 많은 연구가 이루어졌습니다 . 목표는 잠금 또는 대기없는 방식으로 알고리즘을 구현하는 것입니다. 이러한 알고리즘에서 프로세스는 다른 프로세스가 작업을 완료하도록 도와 프로세스가 최종적으로 작업을 완료 할 수 있도록합니다. 결과적으로 일부 작업을 수행하려는 다른 프로세스가 중단 된 경우에도 프로세스가 작업을 완료 할 수 있습니다. Usinig 잠금 장치는 잠금을 해제하지 않고 다른 프로세스가 계속 진행되지 않도록합니다.


받아 들여진 대답을 보았을 때, 나는 당신 이 이론적 관점 을 쓴 것을 볼 때까지 비판적 섹션의 개념을 잘못 기억했을 것이라고 생각했습니다 . :)
— Anirudh Ramanathan

2
실용 잠금없는 프로그래밍은 Shangri La와 유사합니다. Keir Fraser의 논문 (PDF)은 이것을 다소 흥미롭게 탐구합니다 (2004 년으로 되돌아 감). 그리고 우리는 2012 년에도 여전히 어려움을 겪고 있습니다.
— Tim Post

22

다른 답변 외에도 다음 세부 정보는 창의 중요 섹션에만 적용됩니다.

  • 경합이없는 경우 중요한 섹션을 얻는 것은 InterlockedCompareExchange작업 만큼 간단 합니다.
  • 임계 섹션 구조는 뮤텍스를위한 공간을 보유합니다. 처음에는 할당되지 않습니다
  • 임계 섹션에 대한 스레드간에 경합이있는 경우 뮤텍스가 할당되고 사용됩니다. 중요 섹션의 성능은 뮤텍스의 성능으로 저하됩니다
  • 높은 경합이 예상되는 경우 회전 수를 지정하여 중요 섹션을 할당 할 수 있습니다.
  • 스핀 수가있는 임계 섹션에 경합이있는 경우 임계 섹션을 획득하려는 스레드는 해당 프로세서주기 동안 스핀 (통화 대기)합니다. 다른 스레드로 컨텍스트 전환을 수행하는주기 수가 뮤텍스를 해제하기 위해 소유 한 스레드가 취하는주기 수보다 훨씬 높을 수 있으므로 휴면보다 성능이 향상 될 수 있습니다.
  • 스핀 카운트가 만료되면 뮤텍스가 할당됩니다
  • 소유하는 스레드가 임계 섹션을 해제 할 때 뮤텍스가 할당되었는지 확인해야합니다. 할당 된 경우 뮤텍스가 대기 스레드를 해제하도록 설정합니다.

리눅스에서 나는 그들이 스핀 카운트와 중요한 섹션과 유사한 목적을 제공하는 "스핀 잠금"을 가지고 있다고 생각합니다.


불행히도 Window critical 섹션은 커널 모드에서 CAS 작업 을 수행하는데 , 이는 실제 연동 작업보다 훨씬 비쌉니다. 또한 Windows 중요 섹션에는 관련 스핀 수가있을 수 있습니다.
— 2009

2
그것은 사실이 아닙니다. CAS는 사용자 모드에서 cmpxchg로 수행 할 수 있습니다.
— Michael

InitializeCriticalSection을 호출하면 기본 스핀 수가 0이라고 생각했습니다. 스핀 수를 적용하려면 InitializeCriticalSectionAndSpinCount를 호출해야합니다. 당신은 그것에 대한 참조가 있습니까?
— 1800 정보

18

중요 섹션 및 Mutex는 운영 체제별로 다르며 멀티 스레딩 / 멀티 프로세싱의 개념입니다.

중요 섹션 주어진 시간에 자체적으로 만 실행해야하는 코드입니다 (예 : 5 개의 스레드가 동시에 실행되고 "critical_section_function"이라는 함수가 배열을 업데이트합니다 ... 5 개의 스레드를 모두 원하지는 않습니다. 따라서 프로그램이 critical_section_function ()을 실행할 때 다른 스레드는 critical_section_function을 실행해서는 안됩니다.

mutex * Mutex는 임계 섹션 코드를 구현하는 방법입니다 (토큰처럼 생각하십시오. 스레드는 critical_section_code를 실행하기 위해이를 보유해야합니다).


2
또한 프로세스간에 뮤텍스를 공유 할 수 있습니다.
— 구성자

14

뮤텍스는 스레드가 획득 할 수있는 객체로, 다른 스레드가이를 획득하지 못하게합니다. 필수 사항이 아닌 권고입니다. 스레드는 뮤텍스가 나타내는 자원을 획득하지 않고 사용할 수 있습니다.

중요 섹션은 운영 체제에서 작동하지 않도록 보장되는 코드 길이입니다. 의사 코드에서는 다음과 같습니다.

StartCriticalSection();
    DoSomethingImportant();
    DoSomeOtherImportantThing();
EndCriticalSection();

1
나는 포스터가 상호 배제를 제공하는 win32 Critical 섹션 객체와 같은 사용자 모드 동기화 프리미티브에 대해 이야기했다고 생각합니다. 나는 Linux에 대해 모른다. 그러나 Windows 커널에는 설명 할 수있는 중요한 영역이있다-인터럽트 불가능하다.
— Michael

1
왜 당신이 다운 보트를 받았는지 모르겠습니다. 올바르게 설명한 임계 섹션 의 개념 이 있는데 , 이는 뮤텍스 유형 인 CriticalSection이라는 Windows 커널 객체와 다릅니다. OP가 후자의 정의에 대해 묻고 있다고 생각합니다.
— Adam Rosenfield

적어도 언어에 구애받지 않는 태그에 혼란스러워했습니다. 그러나 어쨌든 이것이 기본 클래스와 동일하게 구현 이름을 Microsoft에 부여하는 것입니다. 나쁜 코딩 연습!
— Mikko Rantanen

글쎄요, 그는 가능한 한 많은 세부 사항을 요구했고, 구체적으로 Windows와 Linux를 말했기 때문에 개념과 같은 소리가 좋습니다. +1--1도 이해하지 못했습니다 : /
— Jason Coco

14

Linux에서 중요 선택과 동일한 '빠른'Windows는 futex 이며 이는 빠른 사용자 공간 뮤텍스를 나타냅니다. futex와 mutex의 차이점은 futex의 경우 중재가 필요할 때만 커널이 관여하므로 원자 카운터가 수정 될 때마다 커널과 통신하는 오버 헤드가 절약된다는 것입니다. .. 일부 응용 프로그램에서 잠금을 협상 하는 데 상당한 시간을 절약 할 수 있습니다 .

뮤텍스를 공유하기 위해 사용하는 수단을 사용하여 프로세스간에 퓨 텍스를 공유 할 수도 있습니다.

불행하게도, futex는 구현하기 가 매우 까다로울 수 있습니다 (PDF). (2018 업데이트, 그들은 2009 년만큼 거의 무섭지 않습니다).

그 외에도 두 플랫폼에서 거의 동일합니다. 기아를 유발하지 않는 방식으로 공유 구조에 대한 원 자성 토큰 기반 업데이트를 만들고 있습니다. 남아있는 것은 단순히 그것을 달성하는 방법입니다.


6

Windows에서 중요 섹션은 프로세스의 로컬입니다. 뮤텍스는 프로세스간에 공유 / 액세스 할 수 있습니다. 기본적으로 중요한 섹션은 훨씬 저렴합니다. Linux에 대해서는 구체적으로 언급 할 수 없지만 일부 시스템에서는 동일한 것의 별칭 일뿐입니다.


6

내 2 센트를 추가하기 위해 중요한 섹션은 구조로 정의되고 해당 섹션에 대한 작업은 사용자 모드 컨텍스트에서 수행됩니다.

ntdll! _RTL_CRITICAL_SECTION
   + 0x000 DebugInfo : Ptr32 _RTL_CRITICAL_SECTION_DEBUG
   + 0x004 LockCount : Int4B
   + 0x008 재귀 카운트 : Int4B
   + 0x00c 소유 스레드 : Ptr32 Void
   + 0x010 LockSemaphore : Ptr32 무효
   + 0x014 SpinCount : Uint4B

뮤텍스는 Windows 객체 디렉토리에 생성 된 커널 객체 (ExMutantObjectType)입니다. 뮤텍스 작업은 대부분 커널 모드에서 구현됩니다. 예를 들어, Mutex를 만들 때 커널에서 nt! NtCreateMutant를 호출하게됩니다.


Mutex 객체를 초기화하고 사용하는 프로그램이 충돌하면 어떻게됩니까? Mutex 객체가 자동으로 할당 해제됩니까? 아뇨. 권리?
— Ankur

6
커널 객체에는 참조 카운트가 있습니다. 객체에 대한 핸들을 닫으면 참조 횟수가 감소하고 0에 도달하면 객체가 해제됩니다. 프로세스가 충돌하면 모든 핸들이 자동으로 닫히므로 해당 프로세스에만 핸들이있는 뮤텍스가 자동으로 할당 해제됩니다.
— Michael

그리고 이것이 임계 섹션 객체가 프로세스 바운드되는 이유입니다. 반면 뮤텍스는 프로세스간에 공유 될 수 있습니다.
— Sisir

2

마이클의 훌륭한 답변. C ++ 11에 도입 된 뮤텍스 클래스에 대한 세 번째 테스트를 추가했습니다. 결과는 다소 흥미롭고 단일 프로세스에 대한 CRITICAL_SECTION 객체의 원래 보증을 계속 지원합니다.

mutex m;
HANDLE mutex = CreateMutex(NULL, FALSE, NULL);
CRITICAL_SECTION critSec;
InitializeCriticalSection(&critSec);

LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
LARGE_INTEGER start, end;

// Force code into memory, so we don't see any effects of paging.
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    EnterCriticalSection(&critSec);
    LeaveCriticalSection(&critSec);
}

QueryPerformanceCounter(&end);

int totalTimeCS = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);

// Force code into memory, so we don't see any effects of paging.
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);

QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    WaitForSingleObject(mutex, INFINITE);
    ReleaseMutex(mutex);
}

QueryPerformanceCounter(&end);

int totalTime = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);

// Force code into memory, so we don't see any effects of paging.
m.lock();
m.unlock();

QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    m.lock();
    m.unlock();
}

QueryPerformanceCounter(&end);

int totalTimeM = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);


printf("C++ Mutex: %d Mutex: %d CritSec: %d\n", totalTimeM, totalTime, totalTimeCS);

내 결과는 217, 473 및 19입니다 (마지막 두 시간의 시간 비율은 Michael의 시간 비율과 거의 비슷하지만 내 컴퓨터는 그의 컴퓨터보다 최소 4 년 어리기 때문에 2009 년과 2013 년 사이에 속도가 증가했다는 증거를 볼 수 있습니다. XPS-8700이 나왔을 때). 새로운 뮤텍스 클래스는 Windows 뮤텍스보다 2 배 빠르지 만 여전히 Windows CRITICAL_SECTION 객체 속도의 10 분의 1보다 느립니다. 비 재귀 뮤텍스 만 테스트했습니다. CRITICAL_SECTION 객체는 재귀 적입니다 (한 번의 스레드가 동일한 횟수를 남겨두면 반복적으로 입력 할 수 있습니다).


0

실제 기능 만 사용하는 경우 AC 기능을 재진입이라고합니다.

재진입 함수는 동시에 여러 스레드에서 호출 할 수 있습니다.

재진입 기능의 예 :

int reentrant_function (int a, int b)
{
   int c;

   c = a + b;

   return c;
}

재진입 할 ​​수없는 기능의 예 :

int result;

void non_reentrant_function (int a, int b)
{
   int c;

   c = a + b;

   result = c;

}

C 표준 라이브러리 strtok ()은 재진입 할 ​​수 없으며 동시에 두 개 이상의 스레드에서 사용할 수 없습니다.

일부 플랫폼 SDK에는 strtok_r ()이라는 재진입 버전의 strtok ()이 제공됩니다.

엔리코 밀리오레

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.