C++/C++ 적용 예제

C++ 멀티스레딩(multi-thread) 기본 개념: 세마포어(semaphore)와 while() loop를 사용한 프로세스 병렬 처리 이해하기

쉽코딩 2023. 3. 20.

Multi-threading

멀티스레딩은 여러 실행 스레드가 단일 프로세스 내에서 동시에 실행될 수 있도록 하는 프로그래밍 개념입니다. 스레드는 프로세스 내에서 가장 작은 실행 단위이며 가볍고 독립적인 명령 시퀀스로 생각할 수 있습니다. 각 스레드는 자체 스택 및 프로그램 카운터를 갖지만 동일한 프로세스의 다른 스레드와 메모리 및 파일 핸들과 같은 동일한 주소 공간 및 리소스를 공유합니다.

멀티스레딩의 기본 개념은 여러 작업 또는 작업의 일부를 동시에 실행하여 애플리케이션의 성능과 응답성을 향상시키는 것입니다. 스레드가 여러 코어에 분산되어 병렬로 실행될 수 있으므로 이는 여러 CPU 코어가 있는 시스템에서 특히 유용할 수 있습니다.

 

multi-threading



멀티스레딩과 관련된 몇 가지 주요 개념은 다음과 같습니다.

동시성(Concurrency): 멀티스레딩을 사용하면 여러 스레드가 동시에 실행될 수 있습니다. 즉, 동시에 활성화됩니다. 동시성은 타임 슬라이싱(스레드에 단일 CPU 코어에서 실행할 수 있는 작은 시간 슬롯이 부여됨) 또는 진정한 병렬성(스레드가 여러 CPU 코어에서 동시에 실행됨)을 통해 달성할 수 있습니다.

스레드 생성(Thread creation): 스레드는 C++ 표준 라이브러리의 <thread> 헤더 또는 Windows API와 같은 스레드 라이브러리 및 API를 사용하여 프로그래머가 명시적으로 생성할 수 있습니다.

스레드 동기화(Thread synchronization): 다중 스레드 환경에서는 스레드가 조정되고 안전한 방식으로 공유 리소스에 액세스하는지 확인하는 것이 중요합니다. 뮤텍스, 세마포어 및 조건 변수와 같은 동기화 프리미티브를 사용하여 공유 리소스에 대한 액세스를 제어하고 스레드 실행을 조정할 수 있습니다.

스레드 통신(Thread communication): 스레드는 공유 메모리, 메시지 전달 또는 기타 메커니즘을 통해 서로 통신할 수 있습니다. 이를 통해 데이터를 공유하고 이벤트에 신호를 보내거나 활동을 조정할 수 있습니다.

스레드 종료(Thread termination): 스레드는 실행을 완료하거나 애플리케이션 또는 운영 체제에 의해 명시적으로 종료됨으로써 종료될 수 있습니다.

멀티스레딩을 이해하고 효과적으로 사용함으로써 프로그래머는 보다 효율적이고 응답성이 뛰어나며 오늘날의 멀티코어 프로세서를 활용하는 데 더 적합한 애플리케이션을 개발할 수 있습니다.

 

 


 

이번 포스팅의 C++에서는 뮤텍스나 조건 변수를 사용하지 않고 세마포어(semaphore) 및 while 루프와 함께 멀티스레딩을 사용할 수 있습니다.

→ mutex의 경우 연산 처리 시간이 오래걸립니다. 해당 이유는 참고 영상을 공부하면 알 수 있습니다.

[참고 영상]

1. 스핀락(spinlock) 뮤텍스(mutex) 세마포(semaphore) 각각의 특징과 차이 완벽 설명! 뮤텍스는 바이너리 세마포가 아니라는 것도 설명합니다!, https://www.youtube.com/watch?v=gTkvX2Awj6g  

 

2. 프로세스, 스레드, 멀티태스킹, 멀티스레딩, 멀티프로세싱, 멀티프로그래밍, 이 모든 것을 한 방에 깔끔하게 설명합니다!! 콘텐츠 퀄리티 만족하실 겁니다!, https://www.youtube.com/watch?v=QmtYKZC0lMU&t=29s

 

이 경우 CreateSemaphore, WaitForSingleObject 및 ReleaseSemaphore 함수와 함께 Windows API 세마포를 사용할 수 있습니다. 각 스레드는 작업을 실행하기 전에 세마포어 신호를 기다립니다. WaitForSingleObject 함수는 세마포어를 기다리는 데 사용되며 ReleaseSemaphore 함수는 계속할 수 있는 다른 스레드에 신호를 보내는 데 사용됩니다.

 

아래의 코드에서 각각의 thread는 while() loop와 WaitForSingleObject 그리고 ReleaseSemaphore로 구성되어 있는 것을 확인할 수 있습니다.

thread_1()
thread_2(), thread_3()

 

예시 코드

/* Code Explain
This code demonstrates the use of Windows semaphores to synchronize three threads in C++. 
It simulates a simple pipeline, where each thread performs some work and then signals the next thread to start. 
*/

#include <iostream>
#include <thread>
#include <Windows.h>
#include <stdlib.h>
#include <stdint.h>
#include <ctime>
#include <vector>
#include <stdio.h>

// namespace
using namespace std;

HANDLE sem1, sem2, sem3;

void thread_1() {

	// local variable 
	bool start;
	string user_input;
	clock_t start_time;
	clock_t end_time;

	cout << "If you ready, enter 'start' in the cmd: ";
	cin >> user_input;

	if (user_input == "start") {
		start = true;
	}
	else
	{
		cout << "Invalid input. Please enter 'start' to run the other threads." << std::endl;
	}

	while (start)
	{
		start_time = clock();
		this_thread::sleep_for(chrono::milliseconds(100)); // Sleep for 100 milliseconds to simulate work
		/*
		기능 추가
		*/
		end_time = clock();
		printf("Thread_1 is worked......... Processed Time : %d......... \n", end_time - start_time);

		ReleaseSemaphore(sem1, 1, NULL);
	}

}

void thread_2() {
	clock_t start_time;
	clock_t end_time;
	while (TRUE) {
		WaitForSingleObject(sem1, INFINITE);
		start_time = clock();
		/*
		기능 추가
		*/
		end_time = clock();
		printf("Thread_2 is worked......... Processed Time : %d......... \n", end_time - start_time);
		
		ReleaseSemaphore(sem2, 1, NULL);
	}
}

void thread_3() {
	clock_t start_time;
	clock_t end_time;
	while (TRUE) {
		WaitForSingleObject(sem2, INFINITE);
		start_time = clock();
		/*
		기능 추가
		*/
		end_time = clock();
		printf("Thread_3 is worked......... Processed Time : %d......... \n", end_time - start_time);
	}
}

int main() {

	// Initialize the semaphores
	sem1 = CreateSemaphore(NULL, 0, 1, NULL);
	sem2 = CreateSemaphore(NULL, 0, 1, NULL);

	// Create the threads
	thread t1(thread_1);
	thread t2(thread_2);
	thread t3(thread_3);

	t1.join();
	t2.join();
	t3.join();

	CloseHandle(sem1);
	CloseHandle(sem2);

	return 0;
}

[코드 설명]

이 코드는 Windows 세마포어를 사용하여 C++에서 세 개의 스레드를 동기화하는 방법을 보여줍니다. 각 스레드가 일부 작업을 수행한 다음 시작하도록 다음 스레드에 신호를 보내는 간단한 파이프라인을 시뮬레이트합니다. 코드를 단계별로 분석해 보겠습니다.

▶ Header files and namespace:
코드에는 iostream, thread, Windows.h, stdlib.h, stdint.h, ctime, vector 및 stdio.h와 같은 필수 헤더 파일이 포함되어 있습니다. 또한 std 네임스페이스의 사용을 선언합니다.

 Global variables:
3개의 세마포어 핸들 sem1, sem2 및 sem3이 전역 변수로 정의됩니다. 스레드를 동기화하는 데 사용됩니다.

 Thread functions:

★ thread_1(): 이 함수는 100밀리초 동안 휴면하여 작업을 시뮬레이트합니다. 작업을 완료한 후 작업을 시작하기 위해 thread_2()에 신호를 보내기 위해 sem1을 해제합니다. 이 스레드는 사용자 입력이 "시작"인 동안 루프에서 계속 작동합니다.


★ thread_2(): 이 함수는 thread_1()에 의해 sem1이 해제되기를 기다립니다. 해제되면 thread_2()가 작업을 수행한 다음(현재 비어 있지만 원하는 기능으로 확장할 수 있음) sem2를 해제하여 thread_3()에 신호를 보내 작업을 시작합니다.


★ thread_3(): thread_2()와 유사하게 이 함수는 작업을 수행하기 전에 thread_2()에 의해 sem2가 해제될 때까지 기다립니다(현재 비어 있지만 원하는 기능으로 확장할 수 있음).

 

 Main function:

★ Initializes semaphores: sem1과 sem2는 초기 카운트 0과 최대 카운트 1로 생성됩니다. 두 번째 세마포어인 sem2는 카운트 0으로 초기화되어 첫 번째 스레드가 신호를 보낼 때까지 두 번째 스레드가 대기하도록 합니다.


★ Creates threads: t1, t2 및 t3은 각각 thread_1(), thread_2() 및 thread_3() 함수에 대해 생성됩니다.


★ Joins threads: 각 스레드에 대해 join() 함수가 호출되어 기본 함수가 종료하기 전에 모든 스레드가 완료될 때까지 대기하는지 확인합니다.


★ Closes semaphore handles: 프로그램이 종료되기 전에 sem1 및 sem2에 대한 핸들이 닫힙니다.

 

이 코드는 각 스레드 함수(thread_1(), thread_2() 및 thread_3())에 기능을 추가하여 확장할 수 있습니다. 현재 구조는 스레드가 동기화된 방식으로 작동하도록 보장합니다. 하나의 스레드가 파이프라인을 시뮬레이트하면서 작업을 시작하도록 다음 스레드에 신호를 보냅니다.

 

 

[결과]

 

아래 결과는 thread_1, thread_2 그리고 thread_3 이 병렬로 처리됨을 나타내는 결과입니다.

실행 결과 : 병렬 처리

 

댓글