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


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.