운영 중에 "왜 ps에는 프로세스 하나로 보이는데 top에서는 스레드가 100개 떠 있죠?"라는 질문을 받은 적이 있습니다. 사실 저도 처음엔 PID, TID, PPID, LWP가 뭐가 다른지 헷갈렸어요. Java 애플리케이션 CPU 100% 잡다가 결국 스레드 단위로 봐야 한다는 걸 알게 됐죠. 오늘은 리눅스에서 프로세스와 스레드가 실제로 어떻게 구분되고, 명령어로 어떻게 들여다보는지 정리해볼게요.
개념 / 원리
운영체제 책에서는 보통 이렇게 설명합니다.
- 프로세스(Process): 실행 중인 프로그램. 독립된 메모리 공간(텍스트, 데이터, 힙, 스택)을 가진다.
- 스레드(Thread): 프로세스 내부의 실행 흐름 단위. 같은 프로세스의 스레드들은 메모리를 공유한다.
근데 리눅스는 조금 특이합니다. 커널 입장에서 보면 프로세스와 스레드를 거의 같은 자료구조(task_struct)로 다뤄요. 단지 메모리 공간을 공유하느냐 안 하느냐가 다를 뿐입니다. 그래서 리눅스에선 스레드를 LWP(Light Weight Process) 라고도 부릅니다.
식별자 정리:
| 용어 | 의미 |
|---|---|
| PID | Process ID. 프로세스 대표 ID. |
| TID | Thread ID. 각 스레드의 ID(=LWP). |
| PPID | Parent Process ID. 부모 프로세스 PID. |
| TGID | Thread Group ID. 같은 프로세스의 스레드들이 공유. PID와 같음. |
핵심은 TGID == PID 라는 점입니다. 멀티스레드 프로세스에서 메인 스레드의 TID는 PID(TGID)와 같고, 추가 스레드들은 같은 TGID를 가진 서로 다른 TID를 갖습니다.
실전 예제
1. 프로세스 트리 보기
ps -ef --forest
pstree -p
-p는 PID도 같이 보여줘서 부모-자식 관계 파악에 좋습니다.
2. 특정 프로세스의 스레드 보기
ps -L -p 1234
출력 예:
PID LWP TTY TIME CMD
1234 1234 ? 00:00:01 java
1234 1235 ? 00:00:00 java
1234 1236 ? 00:00:02 javaLWP 컬럼이 TID입니다. PID는 1234로 같은데 LWP가 1234, 1235, 1236으로 다르죠.
3. /proc로 직접 보기
ls /proc/1234/task/
# 1234 1235 1236
/proc/<PID>/task/ 아래에 스레드 디렉토리가 TID별로 있습니다.
cat /proc/1234/status | grep -E "^(Pid|Tgid|PPid)"
출력:
Tgid: 1234
Pid: 1234
PPid: 1추가 스레드 자신을 보면 Pid는 TID, Tgid가 프로세스 PID입니다.
cat /proc/1234/task/1235/status | grep -E "^(Pid|Tgid)"
# Tgid: 1234
# Pid: 1235
4. top에서 스레드 단위 보기
기본 top은 프로세스 단위입니다. 실행 중에 H를 누르면 스레드 단위로 전환됩니다.
top -H -p 1234
-H로 시작부터 스레드 모드.
5. CPU 많이 쓰는 스레드 찾기 (Java 단골 절차)
# 1. CPU 잡아먹는 PID 찾기
top
# 2. 그 PID의 스레드 중 범인 찾기
top -H -p <PID>
# 3. TID를 16진수로 변환
printf "%x\n" <TID>
# 4. jstack으로 매칭
jstack <PID> | grep -A 10 nid=0x<hex>
6. 부모-자식 관계 추적
ps -o ppid= -p 1234
pgrep -P 1234
pgrep -P로 자식 프로세스들을 한 번에 뽑을 수 있습니다.
자주 하는 실수
1. PID와 TID 혼동
kill -9 <TID>로 스레드 하나만 죽이려는 시도. 리눅스에서 시그널은 보통 프로세스(TGID) 단위로 처리되고, 특정 스레드만 죽이는 건 tgkill 시스템 콜이 필요합니다. 일반적인 kill <PID>는 프로세스 전체를 종료시킨다고 보면 됩니다.
2. ps만 보고 스레드 수 판단
ps -ef | grep java
이러면 메인 스레드 하나로만 보입니다. ps -eLf 또는 ps -L -p <PID>로 봐야 모든 스레드가 나옵니다.
3. PPID가 1인 이유
부모 프로세스가 자식보다 먼저 죽으면 자식의 PPID는 1(init/systemd)로 바뀝니다. 이게 바로 고아 프로세스가 init에 입양되는 과정이에요. PPID=1이라고 무조건 데몬은 아닙니다.
4. 좀비 프로세스(Z 상태)에 대한 오해
좀비는 메모리를 잡아먹는 게 아니라 PID 슬롯만 차지합니다. 부모가 wait()로 종료 코드를 회수하면 사라져요. ps에서 <defunct>로 표시됩니다.
마무리
정리하면 세 줄입니다.
- 리눅스에서 스레드는 메모리를 공유하는 LWP일 뿐, 커널 입장에선 프로세스와 거의 같다.
- TGID(=PID)는 프로세스를, TID(=LWP)는 그 안의 스레드를 식별.
/proc/<PID>/task/에 다 있다. - 스레드 단위로 보려면
ps -L,top -H. 부모는 PPID, 자식은pgrep -P로 추적.
'IT·리눅스 > 리눅스' 카테고리의 다른 글
| 리눅스 메모리 - RSS, VSZ, Shared 실제로 얼마를 쓰는 걸까 (0) | 2026.07.06 |
|---|---|
| tmux 입문 - SSH 연결이 끊겨도 작업 살리는 법 (0) | 2026.06.19 |
| find 명령어 실전 활용 - 운영에서 진짜 쓰는 10가지 패턴 (0) | 2026.06.17 |
| 디스크 I/O 모니터링 - iostat과 iotop으로 병목 잡기 (0) | 2026.06.16 |
| /proc 파일시스템 탐험 - 리눅스 내부 상태 들여다보기 (0) | 2026.06.12 |