
다음과 같은 join 코드의 내부 코드 보는 법
디버깅 돌려서 HelloThread() 함수에 중단점 찍기
중단점을 찍은 뒤 쓰레드를 주 스레드로 변경하면
내부코드 볼 수 있다.

뿐만 아니라
스레드 설정을 바꿔가며 다른 스레드들은 뭘 하고 있는지 왔다갔다하며
체크할 수 있다.
Thread 클래스 5가지 함수
사실 Thread 클래스와 관련된 내용들은 사실 복잡하지 않다.
Thread 클래스에서 5가지 핵심 함수를 알면,
그 외에는 크게 신경 쓸 일은 없다.
int main()
{
// HelloThread();
std::thread t(HelloThread);
cout << "Hello Main" << endl;
// 스레드 핵심 함수 5가지
t.hardware_concurrency();
t.get_id();
t.detach();
t.joinable();
t.join();
}
std::thread hardware_concurrency(); - CPU 코어 개수
멀티 코어 환경에서 실질적으로 구동할 수 있는 코어 개수가
어느 정도 CPU에 따라서 고정되어 있다.
그 정보를 추출하는 함수이다.
그런데, 이 함수는 100% 확률로 정확하지는 않다.
만약에 정보를 추출할 수 없다고 하면,
경우에 따라서 0을 리턴하는 경우도 있다.
즉 반환 값으로는
하드웨어 스레드 컨텍스트의 수에 대한 추정치를 반환한다.
만약 해당 값이 계산될 수 없거나 명확하지 않다면, 이 메서드는 0을 반환한다.
사용예시 코드
int32 count = t.hardware_concurrency(); // CPU 코어 개수
std::thread get_id(); - 각 쓰레드 마다 부여되는 ID
id라고 해서 순차적으로 1,2,3,4 씩 늘어나는 건 아니고
약간 들쑥날쑥한 값으로 늘어난다.
하지만 쓰레드 사이에는 겹치는 ID는 없다.
사용예시 코드
auto id = t.get_id(); // 각 쓰레드 마다 부여되는 ID
std::thread join(); - 식별된 쓰레드 작업
join은 현재 쓰레드의 작업 흐름을 동기화로 수행하지 않고
관련 스레드가 완료될 때까지 차단한다.
호출이 성공하면 호출 개체에 get_id 대한 후속 호출
즉 기존 쓰레드와 같지 않은 thread::id 기본값을 반환한다.
호출이 성공하지 않으면 반환되는 get_id값은 변경되지 아니한다.
즉, join 구현 코드를 뜯어 보자면
현재 쓰래드를 *this로 식별되는 스레드의 실행이 완효될 때까지 차단한다는 것이다.
*this로 식별되는 스레드의 완료는 join()의 성공적인 반환과 동기화된다.
*this 자체에 대해서는 동기화가 수행되지 않는다.
"join()은 *this 자체에 대해서 동기화를 수행하지 않는다"는 의미는,
join() 함수가 호출될 때 해당 스레드의 종료를 기다려주는 역할만 한다는 뜻이다.
즉, join()은 연결된 스레드의 종료와
그에 따른 메모리 동기화(예: 스레드가 수행한 작업의 결과를 호출 스레드에서 볼 수 있게 하는)를 보장하지만,
std::thread 객체(*this) 자체에 대해
내부적으로 별도의 동기화(예: 락이나 뮤텍스)를 적용하지 않는다는 말이다.
- 스레드 종료 동기화:
- join()은 대상 스레드가 종료될 때까지 현재 스레드를 대기시키며,
- 스레드 종료 시 그 스레드에서 수행된 작업의 결과가 호출자에게 보이도록 메모리 동기화를 보장한다.
- 객체 접근 동기화 미적용:
- 하지만, join() 내부에서는
- std::thread 객체(즉, join()을 호출한 객체)
- 자체를 보호하기 위한 별도의 동기화 메커니즘이 적용되지 않는다.
- 따라서 여러 스레드가 동시에 같은 std::thread 객체에 대해 join()을 호출하는 경우,
- 이는 동기화되지 않은 상태에서 객체에 대한 동시 접근이 발생하여
- 데이터 경쟁(race condition)이 생길 수 있으며,
- 이는 정의되지 않은 동작(Undefined Behavior)을 초래한다.
std::thread 객체 자체에 대한 동시 접근 문제는 개발자가 별도로 관리해야 한다.
즉, 마지막 말인
"std::thread 객체 자체에 대한 동시 접근 문제는 개발자가 별도로 관리해야 한다."는,
여러 스레드에서 동시에 동일한 스레드 객체에 대해 join()을 호출하는 것은
데이터 경쟁을 일으켜 정의되지 않은 동작(= 오류)을 초래한다.
라는 말과 같다.
++Plus++
왜 굳이 *this냐?
std::thread join도 결국 클래스이고 함수이다.
이 함수를 *this로 접근하게 되면
해당 함수의 개체 그 자체를 의미하고,
해당 개체의 모든 메서드, 멤버에 접근할 수 있기 때문에
*this를 쓴다.
++++
std::tread detach(); - 실제 스레드와 객체 분리
개발자가 만들어준 std::tread 객체 변수와
실질적으로 구동된 스레드랑 연결고리를 딱 끊어주는 것이다.
그러니까, 아예 백그라운드 스레드로 그냥 독립적으로 동작하는 개념이라고 보면 된다.
detach를 해주면 std thread 객체에서
실제 스레드를 분리해준다.
이러한 것을 데몬 프로세스에 응용할 수 잇다.
하지만 Thread의 정보 추출이나 상태를 확인할 수 없기 때문에
사실 평소에는 사용할 일이 없다.
std::tread joinable(); - 연결 가능 여부 확인
bool 값을 리턴하며
현재 연결된 스레드가 연결 가능한지의 여부를 확인해준다.
실제 정의된 함수 정의부분
bool joinable() const noexcept;
// 출저 - microsoft
// https://learn.microsoft.com/en-us/cpp/standard-library/thread-class?view=msvc-170#join
응용해서 생각해보자.
std::thread::detach의 경우에는
joinable은 false를 return하는 것이 정상 작동하는 것이다.
왜? 연결을 끊어주니까.
그럼 std::tread::join() 시,
joinable이 false가 나오면 스레드가 유효하지 않다는 것이고
이는 오류 조건이 된다는 것이다.
그럼 이제,
std::thread t;
이러한 경우는 Thread의 어떠한 기능을 할까?
정답은 아무런 기능을 하지 않는다.
실질적으로 Thread가 실행되지 않는 것이다.
무언가 함수를 호출하고 값을 읽어오고 호출하는 시도를 할 때
Thread 실행이 되는 것이다.
코드와 주석으로 개념 정리
#include "pch.h"
#include <iostream>
#include "CorePch.h"
#include <thread>
void HelloThread()
{
cout << "Hello Thread" << endl;
}
int main()
{
// HelloThread();
std::thread t; // (HelloThread);
cout << "Hello Main" << endl;
int32 count = t.hardware_concurrency(); // CPU 코어 개수
auto id = t.get_id(); // 각 쓰레드 마다 부여되는 ID
t.detach(); // 스레드와 개체의 분리
t.joinable(); // 개체와 연결된 스레드가 join가능 한지?
t.join(); // 개체와 바인딩된 스레드가 종료될 때 까지 기다려준다.
}
응용 코드
#include "pch.h"
#include <iostream>
#include "CorePch.h"
#include <thread>
void HelloThread(int32 num)
{
cout << "Hello Thread" << num << endl;
}
int main()
{
vector<std::thread> v;
for (int32 i = 0; i < 10; i++) {
v.push_back(std::thread(HelloThread, i));
}
for (int32 i = 0; i < 10; i++) {
if (v[i].joinable()) {
v[i].join();
}
}
cout << "Hello Main" << endl;
}
실행 결과를 봐보자,

과연 이 실행 결과가 오류일까?
아니다.
딱히 순서를 정해주지도 않았다.
앞서 말한 바와 같이
join은 연결 여부를 확인하여 실행후 종료까지 기다려 줄 뿐이다.
즉 다시 한 번 말하지만,
join은 *this 자체에 대해서 동기화를 수행하지 않으므로
join 함수가 호출될 때 해당 스레드의 종료를 기다려주는 역할만 수행 한다.
즉, join()은 연결된 스레드의 종료와
그에 따른 메모리 동기화,
스레드가 수행한 작업의 결과를 호출 스레드에서 볼 수 있게 하는 것은 보장하지만,
std::thread 객체(*this) 자체에 대해
내부적으로 별도의 락이나 뮤텍스 같은
실행 흐름 단위의 동기화를 적용하지 않는다는 말이다.
참고 문서
https://en.cppreference.com/w/cpp/thread/thread/join
std::thread::join - cppreference.com
void join(); (since C++11) Blocks the current thread until the thread identified by *this finishes its execution. The completion of the thread identified by *this synchronizes with the corresponding successful return from join(). No synchronization is perf
en.cppreference.com
https://learn.microsoft.com/ko-kr/cpp/standard-library/thread-class?view=msvc-170
thread 클래스
자세한 정보: 스레드 클래스
learn.microsoft.com
https://en.cppreference.com/w/cpp/thread/thread/detach
std::thread::detach - cppreference.com
void detach(); (since C++11) Separates the thread of execution from the thread object, allowing execution to continue independently. Any allocated resources will be freed once the thread exits. After calling detach *this no longer owns any thread. [edit] P
en.cppreference.com
'컴퓨터 프로그래밍 공부 > 네트워크 서버' 카테고리의 다른 글
멀티쓰레드 개론 (0) | 2025.02.25 |
---|---|
정적라이브러리를 이용한 서버 기반 만들기 (0) | 2025.02.24 |
IP(Internet Protocol) IPv4 (0) | 2025.02.04 |
IP(Internet Protocol) 열기 (1) | 2024.12.15 |
LAN을 넘어서는 네트워크 계층의 기능 (0) | 2024.11.16 |