기본적으로 MSYS2의 ucrt64, mingw64 등등이 깔려 있음을 가정한다. 운영체제도 Windows임을 가정한다. (gcc 도)
https://code.visualstudio.com/docs/cpp/config-mingw Visual Studio Code IDE에서 제공하는 mingw64를 다운로드하는 가이드라인이니 참고.
Mutex Thread에 관한 내용이 궁금하다면 다음 게시글 참고
2023.08.10 - [Computer Science/C++] - 멀티 스레드 Multi-thread (+ Windows, MFC)
Mutex Thread (임계 영역 Thread)
보통 한 개의 응용 프로그램 (process)는 여러 개의 thread를 사용한다. 각각의 thread들은 비동기 (asynchronous) 하게 event를 처리하거나 서로 다른 task를 맡아 실행할 수 있다.
<전체 코드>
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <random>
using namespace std;
mutex vectors;
mutex couts;
vector<int> values;
int generateNum(const int& min, const int& max) {
static thread_local mt19337 generator(hash<thread::id>()(this_thread::get_id()));
uniform_int_distribution<int> distribution(min, max);
return distribution(generator);
}
void threadFunction(int id) {
couts.lock();
cout << "Thread Begins " << id << ".\n";
couts.unlock();
vectors.lock();
int vec = vectors[0];
vectors.unlock();
int nVec = generateNum(0, 20);
vec += nVec;
couts.lock();
cout << "Thread " << id << " adding " << nVec << ". New value: " << vec << ".\n";
couts.unlock();
vectors.lock();
vectors.push_back(vec);
vectors.unlock();
}
int main() {
vectors.push_back(72);
thread th1(threadFunction, 1);
thread th2(threadFunction, 2);
thread th3(threadFunction, 3);
thread th4(threadFunction, 4);
th1.join();
th2.join();
th3.join();
th4.join();
cout << "Input: " << vectors[0] << ", Result 1: " << vectors[1] << ", Result 2: " << vectors[2] << ", Result 3: " << vectors[3] << ", Result 4: " << vectors[4] << "\n";
}
코드 분석
변수 선언 및 함수
mutex vectors;
mutex couts;
vector<int> values;
int generateNum(const int& min, const int& max) {
static thread_local mt19337 generator(hash<thread::id>()(this_thread::get_id()));
uniform_int_distribution<int> distribution(min, max);
return distribution(generator);
}
void threadFunction(int id) {
couts.lock();
cout << "Thread Begins " << id << ".\n";
couts.unlock();
vectors.lock();
int vec = vectors[0];
vectors.unlock();
int nVec = generateNum(0, 20);
vec += nVec;
couts.lock();
cout << "Thread " << id << " adding " << nVec << ". New value: " << vec << ".\n";
couts.unlock();
vectors.lock();
vectors.push_back(vec);
vectors.unlock();
}
글로벌 변수로 mutex , vector를 선언한다. generateNum 함수를 통해 vector에 넣을 값을 만든다.
generateNum 함수에서 static thread_local을 살펴보자. 'thread_loca'을 붙여주면 static으로 변수를 선언하더라도 thread 내부로 한정된다. 즉, 각 thread는 다른 generator를 갖게 된다. mt19337은 generator의 기반이 되는 algorithm으로 대중적으로 많이 사용되는 generator algorithm이다.
threadFunction함수는 각 thread가 실행할 함수이고, 인자로 정수형 id를 받는다. mutex에 lock, unlock을 걸어 vectors라는 공유 자원 (데이터)과 출력을 담당하는 'cout'을 보호한다.
메인 함수
int main() {
vectors.push_back(72);
thread th1(threadFunction, 1);
thread th2(threadFunction, 2);
thread th3(threadFunction, 3);
thread th4(threadFunction, 4);
th1.join();
th2.join();
th3.join();
th4.join();
cout << "Input: " << vectors[0] << ", Result 1: " << vectors[1] << ", Result 2: " << vectors[2] << ", Result 3: " << vectors[3] << ", Result 4: " << vectors[4] << "\n";
}
thread를 선언하여 실행하고, join()을 통해 thread를 기다린 후 종료시킨다.
결과
ucrt64를 이용하여 compile하였으며, 또는 상단의 visual studio code IDE tutorial을 그대로 따르면 쉽게 할 수 있다. ucrt64를 이용할 때는 (thread를 사용하므로 POSIX thread의 줄임말인 pthread를 사용함을 잊지 말자) 이렇게 작성하면 된다. 물론 자신의 경로와 파일 이름에 맞게 작성해 주면 된다.
g++ -o 01_01 -std=c++11 -pthread 01_01.cpp
// 첫번째 결과
Strating thread 1.
Strating thread 3.
Strating thread 2.
Strating thread 4.
Thread 1 adding 11. New values: 83.
Thread 3 adding 13. New values: 85.
Thread 2 adding 3. New values: 75.
Thread 4 adding 7. New values: 79.
Input: 72, Result 1: 83, Result 2: 85, Result 3: 75, Result 4: 79
// 두번째 결과
Strating thread 2.
Strating thread 1.
Strating thread 3.
Strating thread 4.
Thread 2 adding 3. New values: 75.
Thread 1 adding 11. New values: 83.
Thread 3 adding 13. New values: 85.
Thread 4 adding 7. New values: 79.
Input: 72, Result 1: 75, Result 2: 83, Result 3: 85, Result 4: 79
// 세번째 결과
Strating thread 1.
Strating thread 2.
Strating thread 3.
Strating thread 4.
Thread 1 adding 11. New values: 83.
Thread 2 adding 3. New values: 75.
Thread 3 adding 13. New values: 85.
Thread 4 adding 7. New values: 79.
Input: 72, Result 1: 83, Result 2: 75, Result 3: 85, Result 4: 79
총 3번의 결과에서 thread가 서로 다른 순서로 실행됨을 볼 수 있다. 또, 매 번 실행마다 같은 thread에서는 vectors에 더하는 값은 동일함을 볼 수 있으며, 각 thread끼리는 그 값이 다름을 확인 가능하다. Asynchronous한 결과를 확인할 수 있으며 각 운영체제나 사양에 따라 다른 결과를 확인할 수 있다.
예를 들어, Starting thread 1. Thread 1 adding 11 New values: 83. Startring thread 2... 이런 형식도 가능하다.
참고 - makefile.txt 를 이용한 compile
ucrt64는 'make'이라는 command가 있으며, Windows 운영체제 용으로는 'mingw32-make'이라는 command가 있다. 지금은 Windows 용으로 만드는 것이 아니니, 'make'을 사용한다.
makefile은 기본적으로 확장자가 정해져 있지 않지만, 본 글에서는. txt로 작성한다. CMake 등 다양한 build 소프트웨어가 시중에 많이 나와있으나 공부 목적으로 직접 makefile을 작성해 보자.
GCC := g++
OUTPUT := 01_01
SOURCES := $(wildcard *.cpp)
CCFLAGS := -std=c++11 -pthread
all: $(OUTPUT)
$(OUTPUT):
$(GCC) -o $(OUTPUT) $(CCFLAGS) $(SOURCES)
clean:
rm $(OUTPUT)
.PHONY: all
어느 정도 보자마자 감이 잡힐 것이다. 헷갈리는 부분을 보면, wildcard는 뒤에. cpp라는 확장자를 가진 모든 file을 의미하며, 따라서 makefile은 build 하고 싶은 file만을 모아서 따로 경로를 지정해 준다. (같은 폴더 내에 위치시킨다.)
'all'은 OUTPUT, SOURCE, CCFLAGS 등 지정한 macro를 이용하여 g++ compiler를 실행한다.
'clean'은 생성된 binary를 제거한다.
'. PHONY'는 폴더 내에 'all'을 실행하지 말고, '. PHONY'에서 언급한 내부 method를 실행한다.
이렇게까지 작성한 후, ucrt64에서 make -f makefile.txt를 작성하면 (폴더 내에서) 똑같이 build를 해준다.
make: nothing to be done for 'all'. 이렇게 error가 뜰 때는, 폴더 내에 이미 만든. exe 파일이 존재할 때이니, 지우고 다시 입력해 주면 된다.
'Computer Science > C++' 카테고리의 다른 글
CMake Shared Lib Linker (0) | 2024.02.26 |
---|---|
알기 쉬운 CMake 개념 정리 및 구조 파악. CMake 튜토리얼 (Tutorial) (1) | 2023.12.22 |
VSCode에서 C++ include 설정하기. include error. MSYS64, UCRT, Mingw (0) | 2023.08.29 |
멀티 스레드 Multi-thread (+ Windows, MFC) (0) | 2023.08.10 |