C 및 Wind32를 사용한 다중 스레딩
MSVC(Microsoft C/C++ 컴파일러)는 다중 스레드 애플리케이션을 만들기 위한 지원을 제공합니다. 애플리케이션에서 사용자 인터페이스가 응답하지 않는 비용이 많이 드는 작업을 수행해야 하는 경우 둘 이상의 스레드를 사용하는 것이 좋습니다.
MSVC를 사용하면 여러 스레드로 프로그래밍할 수 있는 여러 가지 방법이 있습니다. C++/WinRT 및 Windows 런타임 라이브러리, MFC(Microsoft Foundation Class) 라이브러리, C++/CLI 및 .NET 런타임 또는 C 런타임 라이브러리 및 Win32 API를 사용할 수 있습니다. 이 문서에서는 C의 다중 스레딩에 대해 알아봅니다. 예제 코드는 C의 샘플 다중 스레드 프로그램을 참조 하세요.
다중 스레드 프로그램
스레드는 기본적으로 프로그램을 통한 실행 경로입니다. Win32에서 예약하는 가장 작은 실행 단위이기도 합니다. 스레드는 스택, CPU 레지스터의 상태 및 시스템 스케줄러의 실행 목록에 있는 항목으로 구성됩니다. 각 스레드는 모든 프로세스의 리소스를 공유합니다.
프로세스는 하나 이상의 스레드와 메모리에 있는 프로그램의 코드, 데이터 및 기타 리소스로 구성됩니다. 일반적인 프로그램 리소스는 열려 있는 파일, 세마포 및 동적으로 할당된 메모리입니다. 시스템 스케줄러가 스레드 실행 제어 중 하나를 제공하면 프로그램이 실행됩니다. 스케줄러는 실행해야 하는 스레드와 실행 시기를 결정합니다. 우선 순위가 낮은 스레드는 우선 순위가 높은 스레드가 작업을 완료하는 동안 기다려야 할 수 있습니다. 다중 프로세서 컴퓨터에서 스케줄러는 CPU 부하를 분산하기 위해 개별 스레드를 다른 프로세서로 이동할 수 있습니다.
프로세스의 각 스레드는 독립적으로 작동합니다. 스레드를 서로 표시하지 않는 한 스레드는 개별적으로 실행되며 프로세스의 다른 스레드를 인식하지 못합니다. 그러나 공통 리소스를 공유하는 스레드는 세마포 또는 다른 프로세스 간 통신 방법을 사용하여 작업을 조정해야 합니다. 스레드 동기화에 대한 자세한 내용은 다중 스레드 Win32 프로그램 작성을 참조 하세요.
다중 스레딩을 위한 라이브러리 지원
이제 CRT의 모든 버전은 일부 함수의 비 잠금 버전을 제외하고 다중 스레딩을 지원합니다. 자세한 내용은 다중 스레드 라이브러리 성능을 참조하세요. 코드와 연결할 수 있는 CRT 버전에 대한 자세한 내용은 CRT 라이브러리 기능을 참조하세요.
다중 스레딩을 위한 포함 파일
표준 CRT 포함 파일은 라이브러리에서 구현되는 C 런타임 라이브러리 함수를 선언합니다. 컴파일러 옵션에서 __fastcall 또는 __vectorcall 호출 규칙을 지정하는 경우 컴파일러는 레지스터 호출 규칙을 사용하여 모든 함수를 호출해야 한다고 가정합니다. 런타임 라이브러리 함수는 C 호출 규칙을 사용하며 표준 포함 파일의 선언은 컴파일러에 이러한 함수에 대한 올바른 외부 참조를 생성하도록 지시합니다.
스레드 제어에 대한 CRT 함수
모든 Win32 프로그램에는 하나 이상의 스레드가 있습니다. 모든 스레드는 추가 스레드를 만들 수 있습니다. 스레드는 작업을 신속하게 완료한 다음 종료하거나 프로그램 수명 동안 활성 상태를 유지할 수 있습니다.
CRT 라이브러리는 스레드 생성 및 종료를 위해 _beginthread, _beginthreadex, _endthread 및 _endthreadex 함수를 제공합니다.
및 _beginthreadex
함수는 _beginthread
새 스레드를 만들고 작업이 성공하면 스레드 식별자를 반환합니다. 스레드가 실행을 완료하면 자동으로 종료됩니다. 또는 호출 _endthread
을 사용하여 자신을 종료할 수 있습니다._endthreadex
참고 항목
libcmt.lib로 빌드된 프로그램에서 C 런타임 루틴을 호출하는 경우 또는 _beginthreadex
함수를 사용하여 스레드를 _beginthread
시작해야 합니다. Win32 함수 및 CreateThread
.ExitThread
일시 중단된 스레드가 C 런타임 데이터 구조에 대한 액세스를 완료할 때까지 대기하는 스레드가 두 개 이상 차단되면 사용 SuspendThread
으로 인해 교착 상태가 발생할 수 있습니다.
_beginthread 및 _beginthreadex 함수
및 _beginthreadex
함수는 _beginthread
새 스레드를 만듭니다. 스레드는 프로세스의 코드 및 데이터 세그먼트를 프로세스의 다른 스레드와 공유하지만 고유한 레지스터 값, 스택 공간 및 현재 명령 주소가 있습니다. 시스템은 프로세스의 모든 스레드를 동시에 실행할 수 있도록 각 스레드에 CPU 시간을 제공합니다.
_beginthread
Win32 API의 CreateThread 함수와 _beginthreadex
유사하지만 다음과 같은 차이점이 있습니다.
특정 C 런타임 라이브러리 변수를 초기화합니다. 이는 스레드에서 C 런타임 라이브러리를 사용하는 경우에만 중요합니다.
CreateThread
는 보안 특성을 제어하는 데 도움이 됩니다. 이 함수를 사용하여 일시 중단된 상태에서 스레드를 시작할 수 있습니다.
_beginthread
성공한 _beginthreadex
경우 핸들을 새 스레드로 반환하거나 오류가 있는 경우 오류 코드를 반환합니다.
_endthread 및 _endthreadex 함수
_endthread 함수는 생성된 _beginthread
스레드를 종료하고 마찬가지로 _endthreadex
사용자가 만든 _beginthreadex
스레드를 종료합니다. 스레드는 완료되면 자동으로 종료됩니다. _endthread
는 _endthreadex
스레드 내에서 조건부 종료에 유용합니다. 예를 들어 통신 처리 전용 스레드는 통신 포트를 제어할 수 없는 경우 종료할 수 있습니다.
다중 스레드 Win32 프로그램 작성
여러 스레드가 있는 프로그램을 작성하는 경우 해당 동작과 프로그램 리소스의 사용을 조정해야 합니다. 또한 각 스레드가 자체 스택을 수신하는지 확인합니다.
스레드 간에 공통 리소스 공유
참고 항목
MFC 관점에서 유사한 논의는 다중 스레딩: 프로그래밍 팁 및 다중 스레딩: 동기화 클래스를 사용하는 경우를 참조하세요.
각 스레드에는 자체 스택과 CPU 레지스터의 자체 복사본이 있습니다. 파일, 정적 데이터 및 힙 메모리와 같은 다른 리소스는 프로세스의 모든 스레드에서 공유됩니다. 이러한 공통 리소스를 사용하는 스레드는 동기화되어야 합니다. Win32는 세마포, 중요한 섹션, 이벤트 및 뮤텍스를 포함하여 리소스를 동기화하는 여러 가지 방법을 제공합니다.
여러 스레드가 정적 데이터에 액세스하는 경우 프로그램에서 리소스 충돌이 발생할 수 있도록 제공해야 합니다. 한 스레드가 다른 스레드에서 표시할 항목에 대한 x,y 좌표를 포함하는 정적 데이터 구조를 업데이트하는 프로그램을 고려합니다. 업데이트 스레드가 x 좌표를 변경하고 y 좌표를 변경하기 전에 선점된 경우 y 좌표가 업데이트되기 전에 표시 스레드가 예약될 수 있습니다. 항목이 잘못된 위치에 표시됩니다. 세마포를 사용하여 구조체에 대한 액세스를 제어하여 이 문제를 방지할 수 있습니다.
뮤텍스(mutual exclusion의 경우 약식)는 서로 비동기적으로 실행되는 스레드 또는 프로세스 간에 통신하는 방법입니다. 이 통신은 일반적으로 리소스를 잠그고 잠금 해제하여 공유 리소스에 대한 액세스를 제어하여 여러 스레드 또는 프로세스의 활동을 조정하는 데 사용할 수 있습니다. 이 x,y 좌표 업데이트 문제를 해결하기 위해 업데이트 스레드는 업데이트를 수행하기 전에 데이터 구조가 사용 중임을 나타내는 뮤텍스를 설정합니다. 두 좌표가 모두 처리된 후 뮤텍스를 지웁니다. 디스플레이 스레드는 디스플레이를 업데이트하기 전에 뮤텍스가 지워질 때까지 기다려야 합니다. 뮤텍스를 기다리는 이 프로세스는 프로세스가 차단되고 뮤텍스가 지워질 때까지 계속할 수 없기 때문에 종종 뮤텍스에서 차단이라고 합니다.
샘플 다중 스레드 C 프로그램에 표시된 Bounce.c 프로그램은 명명된 ScreenMutex
뮤텍스를 사용하여 화면 업데이트를 조정합니다. 디스플레이 스레드 중 하나가 화면에 쓸 준비가 될 때마다 핸들과 상수 INFINITE를 ScreenMutex
호출 WaitForSingleObject
하여 호출이 WaitForSingleObject
뮤텍스에서 차단되고 시간 초과가 아님을 나타냅니다. 명확한 경우 ScreenMutex
대기 함수는 다른 스레드가 표시를 방해할 수 없도록 뮤텍스를 설정하고 스레드 실행을 계속합니다. 그렇지 않으면 뮤텍스가 지워질 때까지 스레드가 차단됩니다. 스레드가 디스플레이 업데이트를 완료하면 뮤텍스를 호출 ReleaseMutex
하여 해제합니다.
화면 표시 및 정적 데이터는 신중하게 관리해야 하는 리소스 중 두 개에 불과합니다. 예를 들어 프로그램에 동일한 파일에 액세스하는 여러 스레드가 있을 수 있습니다. 다른 스레드가 파일 포인터를 이동했을 수 있으므로 각 스레드는 읽거나 쓰기 전에 파일 포인터를 다시 설정해야 합니다. 또한 각 스레드는 포인터를 배치하는 시간과 파일에 액세스하는 시간 사이에 선점되지 않도록 해야 합니다. 이러한 스레드는 세마포를 사용하여 각 파일 액세스와 ReleaseMutex
호출을 대괄호로 WaitForSingleObject
묶어 파일에 대한 액세스를 조정해야 합니다. 다음 코드 예제에서는 이 기술을 보여 줍니다.
HANDLE hIOMutex = CreateMutex (NULL, FALSE, NULL);
WaitForSingleObject( hIOMutex, INFINITE );
fseek( fp, desired_position, 0L );
fwrite( data, sizeof( data ), 1, fp );
ReleaseMutex( hIOMutex);
스레드 스택
애플리케이션의 모든 기본 스택 공간은 스레드 1이라고 하는 실행의 첫 번째 스레드에 할당됩니다. 따라서 프로그램에 필요한 각 추가 스레드에 대해 별도의 스택에 할당할 메모리 양을 지정해야 합니다. 운영 체제는 필요한 경우 스레드에 대한 추가 스택 공간을 할당하지만 기본값을 지정해야 합니다.
호출의 _beginthread
첫 번째 인수는 스레드를 실행하는 함수에 BounceProc
대한 포인터입니다. 두 번째 인수는 스레드의 기본 스택 크기를 지정합니다. 마지막 인수는 에 전달되는 BounceProc
ID 번호입니다. BounceProc
에서는 ID 번호를 사용하여 난수 생성기를 시드하고 스레드의 색 특성 및 표시 문자를 선택합니다.
C 런타임 라이브러리 또는 Win32 API를 호출하는 스레드는 호출하는 라이브러리 및 API 함수에 충분한 스택 공간을 허용해야 합니다. C printf
함수에는 500바이트 이상의 스택 공간이 필요하며 Win32 API 루틴을 호출할 때 2K바이트의 스택 공간을 사용할 수 있어야 합니다.
각 스레드에는 자체 스택이 있으므로 가능한 한 적은 정적 데이터를 사용하여 데이터 항목에 대한 잠재적 충돌을 방지할 수 있습니다. 스레드에 비공개일 수 있는 모든 데이터에 대해 자동 스택 변수를 사용하도록 프로그램을 디자인합니다. Bounce.c 프로그램의 유일한 전역 변수는 초기화된 후 변경되지 않는 뮤텍스 또는 변수입니다.
Win32는 스레드별 데이터를 저장할 TLS(스레드 로컬 스토리지)도 제공합니다. 자세한 내용은 스레드 TLS(로컬 스토리지)를 참조하세요.
다중 스레드 프로그램으로 문제 영역 방지
다중 스레드 C 프로그램을 만들거나 연결하거나 실행할 때 발생할 수 있는 몇 가지 문제가 있습니다. 가장 일반적인 문제 중 일부는 다음 표에 설명되어 있습니다. (MFC 관점에서 유사한 논의는 다음을 참조하세요.다중 스레딩: 프로그래밍 팁.)
문제 | 가능한 원인 |
---|---|
프로그램에서 보호 위반이 발생했음을 보여 주는 메시지 상자가 표시됩니다. | 많은 Win32 프로그래밍 오류로 인해 보호 위반이 발생합니다. 보호 위반의 일반적인 원인은 데이터를 null 포인터에 간접적으로 할당하는 것입니다. 이로 인해 프로그램에서 해당 메모리에 속하지 않는 메모리에 액세스하려고 하므로 보호 위반이 발생합니다. 보호 위반의 원인을 쉽게 감지하는 방법은 디버깅 정보를 사용하여 프로그램을 컴파일한 다음 Visual Studio 환경에서 디버거를 통해 실행하는 것입니다. 보호 오류가 발생하면 Windows에서 컨트롤을 디버거로 전송하고 커서가 문제를 일으킨 줄에 배치됩니다. |
프로그램에서 수많은 컴파일 및 링크 오류를 생성합니다. | 컴파일러의 경고 수준을 가장 높은 값 중 하나로 설정하고 경고 메시지에 주의하여 많은 잠재적인 문제를 제거할 수 있습니다. 수준 3 또는 수준 4 경고 수준 옵션을 사용하여 의도하지 않은 데이터 변환, 누락된 함수 프로토타입 및 비 ANSI 기능 사용을 검색할 수 있습니다. |
참고 항목
이전 코드를 위한 다중 스레드 지원(Visual C++)
C의 샘플 다중 스레드 프로그램
스레드 로컬 스토리지(TLS)
C++/WinRT를 통한 동시성 및 비동기 작업
C++ 및 MFC에서 다중 스레딩