[OS] 네트워크 복습 + 프로세스와 스레드
- OS
- 2025. 11. 9. 23:25
[복습]
지금까지 네트워크를 학습을 하였다. 그래서 그것들을 간단하게 복습하고 이번에는 어떤것을 대략적으로 학습할 수 있을지 생각해보자우리가 네트워크에서 가장먼저 학습하는 부분은 OSI 7계층이다. 7계층 같은 경우는 이론적으로 위대하신 선배님들이 정리해놓은것이라고 합니다. 하지만 문제는 7계층 같은 경우, 데이터의 흐름 신호를 설명하기에는 부족했다. 그러니까 실무적인 관점이 아니라는 뜻이라고 생각하면 됩니다. 그래서 만든 TCP/IP계층이 등장헀습니다. TCP/IP도 전반적으로 OSI와 다를것은 없습니다. 다만 여기서 중점적으로 봐야 하는 부분은 계층 신호입니다. 계층은 물리 -> 데이터 링크 -> 네트워크 -> 전송 -> 응용계층으로 되어있습니다. 신호란 전기신호입니다. 결국 모든 신호는 0과 1로 되었다고 해도 무방합니다. 하지만 어느 계층에서 신호가 발생하는지 전혀 알 수가 없습니다. 그래서 각 신호가 계층을 지나갈때 신호명을 변경하였습니다. 물리는 물리적 신호이지만 나머지 신호가 논리적 신호라고 불리는 이유도 그 때문입니다. 물리는 비트, 데이터 링크는 프레임, 네트워크는 패킷, 전송은 세그먼트와 데이터그램, 응용은 메시지로 구분합니다. 데이터 신호가 각 계층을 지나면서 헤더도 바뀌고 내용도 바뀌는 영겹의 시간을 거칩니다. 계층에는 각자 중요한 프로토콜이 존재합니다. 이거까지 말하면 길어지기 때문에 넘어가도록 하겠습니다. 우리는 웹 개발자입니다. 그렇기 때문에 제일 잘 알아야 하는 프로토콜은 누가 뭐래도 HTTP일겁니다. HTTP는 2.0까지 TCP위에서 동작을 하였지만 모종의 이유로 인해 3.0은 UDP위에서 동작하게 되었습니다. 그 이유는 구글에서 QUIC라는 프로코톨을 만들어서 UDP가 가진 단점들을 어느정도 해결했다고 합니다. 최초의 HTTP는 단순히 문서 파일을 전달하는 목적이었다고 합니다. 인간의 욕심은 끝이 없기 때문에 문서 파일 뿐만 아니라, 생성도 하고 싶고 수정도 하고 싶어졌습니다. 이것을 해결하기 위해 method를 사용할 수 있습니다.(method는 초창기부터 있었습니다. 0.9에서는 GET만 있었다고 합니다.) 자세한 사항은 https://b-programmer.tistory.com/474, https://b-programmer.tistory.com/475 요기서 확인하면 됩니다. http는 치명적인 문제를 가지고 있습니다. 그것이 뭘까요? 바로 HTTP는 무상태 프로토콜이라는 점입니다. 이말이 뭐냐라고 하면 상태값을 저장하지 않는다는 뜻이 입니다. 이렇게 되버리면 실시간성으로 보여줘하는 데이터를 보여주기 쉽지 않습니다. 이것을 해결하기 위해 웹소켓이라는 프로토콜이 등장하였습니다. (여담이지만 웹소켓은 비교적 최근에 나온 프로토콜입니다.) 웹소켓의 가장 큰 특징은 연결성에 있습니다. 그러니까 다시 말하면 상태값을 가지게 된다는 뜻입니다. 이렇게 간략하게 제가 네트워크를 어떤것을 학습하였는지 작성했습니다.
OS 첫번째!! 프로세스와 스레드
프로세스가 뭐고 스레드가 뭘까요?
가장 간단하게 생각했을때 프로세스는 공장이고 스레드는 일꾼이라고 합니다. 조금만 더 생각해봅시다. 이게 전부일까요? 메인 즉 프로세스가 일을 시키고 그꺼가지는 좋습니다. 중요한건 사실 와닿지 않는게 제일 큽니다. ㅜㅜ
하지만 프로세스와 스레드를 안다는건 언어나 기술을 학습할때 굉장히 유리하다고 생각합니다. 또한 궁금증도 만들 수 있죠.
그것의 사례로 레디스의 싱글 스레드를 들을수 있습니다. 이거에 대해 저도 공부하는 입장이라 공부를 해야 하지만 만약, 스레드를 학습한 이후에 레디스의 싱글 스레드를 본 거와 공부하지 않고 레디스의 싱글 스레드를 바라보는 시선이 다르지 않을까요?

가장 기본적인 모양은 이렇습니다. 생각을 해봅시다. 프로세스의 성능이 좋아지는게 좋을까요? 아니면 쓰레드의 갯수를 늘리는게 좋을까요?프로세스는 실행중인 프로그램을 뜻합니다. 우리가 아는 가장 큰 프로그램은 당연컨데 OS입니다. 그런데 OS안에는 수 많은 프로세스가 동작합니다.
비유를 들자면 마을안에는 병원, 학교 등등이 존재합니다. 마을이 OS라면 병원, 학교 등등도 프로세스죠. 왜냐하면 병원, 학교는 고정된 공간과 자원을 가지고 독립적으로 운영되기 때문입니다. 반면, 병원 안에서 환자를 진료하거나 청소를 하고, 학교 안에서 수업을 진행하는 사람들처럼 실제로 움직이며 일을 수행하는 존재가 바로 스레드(Thread)입니다. 여기서 오해하면 안 되는게 프로세스는 안에 쓰레드가 존재하는것까지 포함 입니다. 즉, 병원안에 의사와 간호사.. 환자까지 포함이 되어야 된다는거죠. 그러면 공간은 존재만 하는 경우는 어떻게 할까요? 현실 세계에서는 폐 건물이 되거나 철거하게 됩니다. 폐 건물인 상태를 시스템 세계에서는 이런 프로세스를 좀비 프로세스라고 부릅니다.

프로세스 상태
위에서 간단하게 프로세스가 존재상태만 있는 상태를 좀비 프로세스라고 불렸습니다. 그렇다면 다른 상태는 어떤것이 존재할까요?
건물에서 쓰레드가 열심히 동작한다면, 잠시 휴업을 한다면, 건물을 철거한다면...
시스템 세계에서는 어떻게 부를까? 각각 Running, Waiting, Terminated(Reaped) 상태라고 합니다.
그러면 궁금한게 생겼습니다. 좀비 프로세스가 된 이후에 그 자리에 다른 프로세스가 들어설 수 있을까요? 아쉽게도 불가능하다고 합니다.
현실에서는 폐업 이후에도 새로운 사업체가 들어설수 있지만 시스템 세상에서는 이러한 것이 불가능하다고 합니다.
좀 더 깊숙이
그 이유는 PID 때문입니다. 애초에 프로세스는 논리적인 단위입니다. PID (Process ID)가 과연 무엇을 뜻할까요? 프로세스의 고유 번호라고 합니다. 그러니까 좀비가 된 이후에도 프로세스 ID는 제거가 되지 않는다는 뜻이 됩니다. 그럼 어떻게 해야 할까요? 바로 프로세스를 철거를 시켜야 합니다. 이는 시스템에 나는 더 이상 이 PID를 사용하지 않는다는 것을 말하는 꼴이 됩니다. 간단하게 메모리를 지우면 될까요? 철거 명령어는 wait()라고 합니다. 갑자기 기다리라니 이게 무슨 소리죠? 음... 대략적인 흐름은 다음과 같다고 합니다.
- 자식 프로세스가 끝나면 exit()을 호출합니다.
→ 실행은 끝났지만, 커널 안에 PCB/PID가 남습니다 (좀비 상태). - 부모 프로세스가 wait()을 호출합니다.
→ 커널은 자식의 종료 상태(exit code)를 부모에게 전달하고 PCB/PID를 삭제합니다. - 이때 PID가 회수(reclaim) 되어, 새 프로세스가 그 번호를 다시 쓸 수 있게 됩니다.
아니 잠깐만요. 커널 이게 뭐죠? 커널이라는 새로운 용어가 나왔는데 저도 모르게 언급을 해버렸네요.. 커널은 시스템 세계에서 두뇌를 뜻합니다. 두뇌라는 소리에 많이들 CPU를 떠올렸을 겁니다. 하지만 CPU는 커널에서 명령을 받아 실제로 동작하는 역할을 합니다. 자자 다시 돌아와서 우리는 프로세스와 스레드에 대해 학습하고 있습니다. 스레드도 명령을 받아서 동작하는 역할이라고 알고 있습니다. 그러면 여기서 또 개념의 충돌이 발생해 버리는데요. 어떻게 보면,CPU는 일을 하는 기계이고, 스레드는 그 기계가 따라야 할 작업 지시서다라고 볼 수 있습니다.
커널이란?
이런 생각을 할 수도 있습니다. “프로세스가 직접 하드웨어에 접근하면 안 될까? 그러면 훨씬 편해지지 않을까?”
결론부터 말씀드리면, 그렇게 하면 안 됩니다. 이유는 보안과 안정성 때문입니다.
현실 세계에서도 법과 제도가 존재하는 이유가 바로 이 두 가지죠. 아무나 마음대로 행동하면 사회가 무너지는 것처럼,
프로세스가 하드웨어에 직접 접근하면 시스템 전체가 불안정해집니다. 이러한 통제와 조정 역할을 담당하는 존재가 바로 커널(Kernel)입니다. 커널은 운영체제의 핵심으로, 모든 프로세스가 하드웨어에 접근할 때 반드시 그를 통해 요청하도록 설계되어 있습니다.
멀티 프로세스와 멀티 스레드
멀티 스레드는 대량적으로 스레드가 여러 개인 상태인 걸로 이해하는데 멀티 프로세스는 어떤 상태를 말할까요? 사실 멀티 프로세스란 말은 없는 말입니다. 정확한 기술 용어는 멀티프로세싱(Multiprocessing)입니다. 다만, ”멀티프로세스”라는 표현이 일상적으로 퍼진 이유는
사람들이 결과(프로세스가 여러 개 돌아가는 상태)를 말할 때 ‘멀티프로세싱’보다 짧고 직관적인 표현을 선호하기 때문이에요.
그렇다면 멀티 스레드도 같을까요? 아쉽게도? “멀티스레딩”은 동작 원리(기술), “멀티스레드 프로그램”은 그 기술을 적용한 결과(상태)를 말합니다.
멀티 프로세싱
그렇다면, 멀티 프로세싱이라는 건 프로세스를 동시에 여러 개를 돌릴 수 있다는 것을 말할까요?
여기서 멀티 프로세싱이라는 용어를 이해하려면, 물리적 병렬과 논리적 병렬(동시성)을 이해를 해야 합니다.
잠깐!!! 병렬이란 무엇일까요? 바로 일을 동시에 하는 것을 뜻합니다. 그러면 의문을 가질 수 있습니다. 일을 동시에 하는 건 하는 거지 왜 물리적이랑 논리적으로 나누었냐라고 할 수 있습니다. 사실 물리적 병렬이라는 말이 실제로 동시에 동작한다는 말이고 논리적 병렬은 동시에 하는 것처럼 보이는 것을 말한다고 합니다.
물리적 병렬 vs 논리적 병렬(동시성)
물리적 병렬이 가능해지려면, ‘동시에 명령을 실행할 수 있는 독립된 연산 유닛(Execution Unit)’이 복수 개 존재해야 합니다.
우리가 흔히 들어본 코어(Core)가 바로 그 연산 유닛을 뜻합니다.
예를 들어, 1 Core, 4 Core라는 표현은 CPU 내부에 서로 독립적으로 명령을 처리할 수 있는 연산 유닛이 하나, 혹은 네 개 존재한다는 의미입니다. 따라서 ‘4 코어 CPU’는 4개의 명령을 물리적으로 동시에 실행할 수 있는 병렬 구조를 가지고 있다고 볼 수 있습니다.
그러면 코어수가 많은 게 좋을까요? 아닐까요? 코어가 많아진다는 건 그만큼 능력이 있다는 뜻입니다. 하지만 그렇게 되는 경우 쉽게 뻗을 수도 있습니다. 아무래도 동시에 관리 비용과 리스크도 폭발적으로 증가합니다. 그러니까 능력이 안되면 나대지 말라는 뜻입니다.
본인의 역량에 맞는 병렬성을 키우는 게 핵심입니다.
하지만 코어가 4개라고 해서 프로세스를 4개만 돌릴 수 있는 것은 아닙니다. 그렇게 하면 CPU의 전체 성능을 온전히 끌어내지 못하기 때문이죠. 이럴 때 사용되는 것이 바로 논리적 병렬(Concurrency)입니다.
운영체제는 코어 수보다 훨씬 많은 프로세스를 동시에 실행해야 하기 때문에, 실제로는 CPU가 아주 짧은 시간 단위로 여러 프로세스를 번갈아가며 실행합니다. 이렇게 빠른 전환으로 인해 여러 프로세스가 동시에 실행되는 것처럼 보이는 상태, 이것이 바로 논리적 병렬, 즉 동시성(Concurrency)입니다.
그러면 동시에 보이게 하는 이 메커니즘에 대해 알아봅시다.
결론부터 말씀드리자면, CPU가 교대로 빠르게 일하는 착시 기술을 뜻합니다. 실제로는 하나의 프로세스만 동작하지만 매우 빠른 시기에 그것을 교체하는 것을 의미합니다. 이걸 가능하게 하는 핵심은 시분할과 콘텍스트 스위칭입니다.
시분할
- CPU의 시간을 아주 짧은 단위(보통 수 밀리초)로 쪼개서 여러 프로세스가 번갈아 사용하는 방식.
콘테스트 스위칭
- 한 프로세스를 잠깐 멈추고, 다른 프로세스로 바꿔 탈 때 그 프로세스의 상태(Context)를 저장했다가 다시 복원하는 기술.
결국, 시분할로 프로세스를 왔다 갔다 하고 콘테스트 스위칭으로 프로세스를 바꾸는 걸로 이해가 되는군요. 시분할은 전략이고, 콘텍스트 스위칭은 행위라고 이해하면 될 거 같습니다.
그렇다면, 1 core인 CPU는 멀티 쓰레딩을 지원을 할까요? 고민해도 좋을 거 같아 이렇게 질문드립니다.
프로세스 간의 통신은?
그렇다면, 프로세스간의 통신은 어떻게 할까요? 위에서는 간단히 동시성이 어떻게 보여줄지 말한 거라면 이번에 공부할 건 프로세스끼리 어떻게 데이터를 주고받을 수 있을지 고민해봐야 합니다. 데이터를 가져다 사용하는 목적인 경우, 프로세스를 바꾸면서 까지 할 필요는 없으니까요. (프로세스 간의 통신 -> IPC: Inter-Process Communication)
추후에 공부할 거지만 이렇게 생각할 수 있을 겁니다. 데이터 공유는 프로세스보다 스레드가 낫지 않을까요? 하지만 스레드는 안전성이 낮다고 합니다. 즉, 안전성이 필요하다면 스레드보다는 프로세스로 개발하는 것이 낫다고 할 수 있죠. (여담이지만 브라우저의 탭은 각각의 프로세스로 구성되어 있습니다. 그래야 독립성을 보장하거든요..)
방법에 대해 고민해 봅시다. 총 4가지 방법이 있다고 합니다.
파이프, 메시지 큐, 공유 메모리, 소켓 이렇게 존재합니다. (참고로 웹 소켓도 이 소켓에서 따왔다고 한다.)
파이프
- 한 방향 데이터 스트림으로 부모-자식 프로세스처럼 연결 관계가 있는 프로세스 간 통신에 사용합니다.
메시지 큐
- 커널이 큐(Queue) 자료구조를 유지하면서 프로세스 간 메시지를 교환하게 해주는 방식입니다.
공유 메모리
- 두 프로세스가 동일한 메모리 영역을 함께 접근할 수 있도록 OS가 허용하는 방식.
소켓
- 네트워크 기반 양방향 통신.
- 프로세스가 같은 머신이든, 완전히 다른 머신이든 IP/Port를 통해 통신할 수 있게 합니다.
궁금한 점은 레빗 mq, 카프카 등등에서 말하는 메시지 큐도 여기에서 말하는 메시지 큐랑 동의어일까? 아쉽지만 아니라고 합니다. 개념적 뿌리만 같을 뿐 전혀 다른 용어라고 합니다. (Kafka나 RabbitMQ는 “IPC 메시지 큐”의 개념을 분산 환경으로 확장한 상위 레벨 기술)
| 분류 | 커널 | 방향 | 속도 | 난이도 | 대표 예시 |
| Pipe | O | 단방향 | 중간 | 쉬움 | `ls |
| Message Queue | O | 양방향(논리) | 중간 | 중간 | Daemon 간 메시지 교환 |
| Shared Memory | O (초기 설정만) | 양방향 | 🔥 가장 빠름 | 어려움 (Lock 필요) | DB 캐시 |
| Socket | O | 양방향 | 중간~빠름 | 쉬움~중간 | 클라이언트/서버 구조 |
여기까지 멀티 프로세싱에 대해 학습하였다. 이제 멀티 쓰레딩에 대해 학습해 보자.
멀티 스레드
위에서 멀티 프로세스라는 말을 한 적이 있습니다. 그래서 멀티 스레드도 없다고 생각할 수 도 있겠지만 멀티 쓰레딩 멀티 쓰레드 모두 존재하는 용어입니다. 멀티 스레딩이라는 말은 하나의 프로세스 안에서 여러 스레드를 동시 실행하도록 만드는 방식이고, 멀티 스레드는 여러 스레드가 동시에 실행 중인 프로그램 상태라고 합니다. 다시 말하면 멀티 쓰레딩은 (기법) / 멀티 스레드는 (상태)입니다.
뭐 대략적으로 알아봤으니 이제 본격적으로 공부해 봅시다.
멀티 스레드라는 말은 뭘까요? 멀티 프로세싱처럼 여러 스레드를 조작할 수 있는 능력을 읽힐까요?
애초에 그런 능력을 뜻하는건 맞습니다. 그렇다면 차이점을 말하면, 멀티 쓰레드는 동일 프로세스 하위에 동작하는 쓰레드들입니다.
의문이 듭니다. 프로세스안에 N개의 쓰레드가 존재한다고 해서 전부 사용할 수 있을지도 의문이 듭니다.
아쉽게도 아니라고 합니다. 프로세스를 설명할때 Core라는 용어를 들었던적이 있었는데요.
CPU core수가 물리적 한계라고 합니다. 이는 Core의 수만큼 쓰레드가 동시에 동작을 시킬 수 있다는 뜻입니다.
그러면 이런 생각이 들 수 있습니다. Core가 프로세스를 말하는건지 쓰레드를 말하는건지... 다행스럽게도, Core는 프로세스나 스레드 같은 ‘소프트웨어 단위’를 지칭하는 말이 아닙니다. Core는 CPU 안에 존재하는 물리적 연산 장치(Execution Unit) 를 뜻합니다.
프로세스나 스레드가 ‘무엇을 실행할지’를 정의한다면, Core는 ‘그것을 실제로 실행하는 주체’입니다.
그래서 프로세스때 학습한 물리적 병렬, 논리적 병렬(동시성)을 전부가능 하다고 합니다.
차이점들은 굉장히 많겠지만 본론적으로 들어갔을때 프로세스는 독립적인 메모리 공간입니다. 하지만 쓰레드는 다릅니다. 독립적인 메모리 공간이 아닙니다. 그렇기때문에 신경을 써야 하는 부분이 존재한다고 생각합니다. 그래서 프로세스끼리 통신하는 경우에는 파이프, 소켓등 다양한 방법으로 통신을 했었습니다. 하지만 쓰레드는 다릅니다. 쓰레드는 공통된 프로세스에서 동작을 하게 되니.
공유되는 자원(shared resource) 이 존재할 수 있습니다.(단, 모든 스레드가 동일 자원을 반드시 공유하는 것은 아닙니다 — 공유 여부는 설계에 따라 달라집니다.)
공유여부는 설계에 따라 달라진다라... 이게 무슨 말일까요?
일단 요걸 이해하기 위해서는 프로스세의 메모리 구조에 대해 이해할 필요가 있을거 같습니다.
| 메모리 | 영역스레드 간 공유 여부 | 설명 |
| Code | ✅ 공유 | 같은 프로그램 코드를 실행함 |
| Data | ✅ 공유 | 같은 전역 변수/정적 변수 접근 가능 |
| Heap | ✅ 공유 | 동적 할당된 객체 공유 가능 |
| Stack | ❌ 독립 | 각 스레드만의 지역 변수, 호출 정보 |
통상적으로 스레드를 일꾼으로 비유를 많이합니다. 고로 저도 일꾼이라고 표현을 하겠습니다.
프로세스는 code, data, heap, stack으로 구분되어있습니다.
간략하게 정리해봅시다.
Code = 프로그램이 어떻게 동작해야 하는지에 대한 설계도
Data = 프로그램이 항상 들고 있어야 하는 상태 정보
Heap = 프로그램이 실행 중에 만들어내는 임시 결과물들
프로세스 내부의 Code, Data, Heap 영역은
모두 스레드 간에 공유된다는 특징을 가지고 있습니다.
즉, 여러 스레드가 같은 코드(Code) 를 실행하고,
공통된 전역 데이터(Data) 에 접근하며,
동일한 힙(Heap) 공간에서 객체를 생성하고 참조한다는 뜻이죠.
-gpt-
스택은 일부러 작성하지 않았습니다. 왜냐하면 스택은 쓰레드의 정체성이기때문에 추후에 설명 하려고 일부러 작성하지 않았습니다.
어쨌든, 현시대에는 수많은 프로그래밍 언어들이 존재합니다. C, Java, Python, Go 등 각 언어가 탄생한 이유는 단순히 문법이 다르기 때문이 아닙니다. "메모리와 CPU에 명령을 어떻게 전달할 것인가", 즉, 메모리 모델과 실행 전략을 어디에 초점을 맞출 것인가에 따라 각기 다른 철학으로 개발된 것입니다. 이렇게 보면, 언어의 차이는 결국 '하드웨어 제어권을 어디까지 허용하느냐' 의 차이라고 이해할 수 있습니다.
다시 본론으로 돌아와서 스택에 대해 학습을 해봅시다. 스택은 스레드마다 고유하게 가질수있는 자원이라고 합니다. 조금더 자세히 들여다볼까요?
스택(Stack)은 스레드의 정체성이라 생각합니다.
각 스레드는 자기 전용 스택을 하나씩 갖고, 이게 곧 "그 스레드가 어디까지 실행했고 무엇을 들고 다니는지"를 규정합니다.
여기까지 대략적으로 프로세스의 메모리구조에 알아봤습니다.
이제 얘네를 공유한다고 생각해봅시다. code, data,heap모두 스레드간의 공유가 발생한다고 합니다. 그렇다면 점유는 어떻게 하는걸까요?
이러한 개념이 바로 동기화입니다.
동기화 (Synchronization)
멀티스레드 환경에서 발생하는 가장 위험한 문제는 경쟁 조건(Race Condition) 입니다. 경쟁 조건은 서로 다른 스레드가 같은 자원을 점유하려고 할때 어떤 스레드가 먼저 점유할지 애매해집니다. 이러한 문제를 경쟁 조건 레이스 컨디션이라고 부릅니다.
동기화란, 공유된 자원을 여러 스레드가 동시에 사용할 때, 데이터의 일관성과 실행 순서를 보장하기 위한 제어 행위입니다.
그렇다면 어떻게 보장할 수 있을까요?
3가지 레벨로 순서를 보장할 수 있다고 합니다.
첫 번째, 하드웨어 레벨 - 원자성(Atomicity)이 있습니다. CPU는 기본적으로 한 번에 한 명령어만 실행합니다. 따라서 어떤 연산을 원자적으로 보장하기 위해 CPU 명령 단위에서는 분리 불가능하게 묶는 것이 첫 단계입니다. 종종 등장하는것이 CAS입니다. 이건 CPU가 제공하는 하드웨어 명령으로 한 번의 명령 안에서 값 비교 + 교체를 동시에 수행할 수 있습니다. 이는 다른 스레드가 끼어들 여지를 하드웨어 단에서 봉새하는것을 뜻합니다. ex) 자바의 AtomicInteger 나 Go의 sync/atomic 패키지가 바로 이 원리를 씁니다.
의문이 듭니다.
이게 동기화랑 무슨 관련이지라고 생각할 수 있습니다. 동기화라는건 서로 다른 공간을 같은 상태로 만드는것을 말합니다. 그리고 원자성은 더 이상 나누어지지 않는 최소한의 단위를 원자성이라고 하죠. 이 두개가 어떻게 보면 서로 상관이 없다고 생각할 수 있습니다. 하지만 두개를 합쳐 봅시다. 그러면 이런 문장을 완성 시킬 수 있습니다. 서로 다른 공간을 같은 상태로 더 이상 나누어지지 않는 최소한의 단위로 존재할 수 있다. 결국, 그 같은 상태로 만드는 것 자체를 하나의 상태로 보는거죠. 예를 들어, 은행에서 1000원을 출금한다고 해봅시다.
이 작업은 "은행 서버의 잔액 감소”와 “ATM에서 현금이 빠져나오는 과정"이 동시에 이루어져야 합니다.
만약 원자성이 깨진다면, 1000원이 인출되는 중간에 오류가 발생해 잔액은 줄었는데 돈이 나오지 않거나, 돈은 나왔는데 잔액이 그대로일 수도 있습니다.
즉, 원자성은 하나의 상태 변경을 쪼개지 않게 하는 힘이고, 동기화는 그 상태가 모든 공간에서 동일하게 보이게 하는 과정입니다.결국 원자성이 깨지면, 동기화된 '같은 상태' 자체가 존재할 수 없습니다.
그래서 원자성은 동기화를 실현하기 위한 필수 전제 조건입니다.
두 번째, OS 레벨 - 순서성(Ordering) 즉, 누가 언제 실행할지를 제어를 할 수 있게 하는것입니다. OS 레벨에서는 순서성(Ordering) 을 통해 동기화를 보조합니다. 운영체제는 스레드가 언제 실행되고, 어떤 순서로 자원에 접근할지를 제어합니다.
이렇게 실행 시점을 조정함으로써, 여러 스레드가 동시에 같은 자원을 건드리는 상황을 방지하고
결과적으로 데이터가 일관된 상태로 유지되도록 돕습니다.
그렇다면 어떻게 순서를 보장할 수 있을까요? 이것은 락 또는 스케쥴링을 통해 해결이 가능합니다. 일단 락 먼저 알아봅시다.
락은 잠그는 행위입니다. 잠근다는건 무엇일까요? 네 맞습니다. 하나의 자원에 대해 여러 스레드가 동시에 접근한다면, 접근하는 스레드 혹은 접근하지 않는 스레드들을 전부 잠구는 행위라 할 수 있습니다. 락은 하나의 스레드가 자원을 점유하는 동안, 나머지는 대기시키는것을 말한다고 합니다. 추가적으로 스케쥴링은 락이 해제되었을 때, 어떤 스레드가 다음으로 실행될지 결정이라 합니다.
락에 대해 알아봤습니다. 락은 대기 시키는 스레드를 잠구는 행위라 배웠습니다. 그렇다면, 어떻게 잠글까여?
상황을 생각해봅시다. 2가지 상황을 생각할 수 있을거 같은데요.
1. 하나만 들어오게 잠그는 방법 → 내가 들어왔으니, 다른 스레드는 전부 문 앞에서 기다려야 하는 방식입니다.
이는 한 번에 한 스레드만 자원을 사용할 수 있도록 하는 뮤텍스(Mutex) 형태의 락입니다.
"상호 배제(Mutual Exclusion)" 라는 말 그대로, 한 명이 쓰는 동안에는 다른 누구도 들어올 수 없습니다.
2. 정해진 인원까지만 허용하고 잠그는 방법 → "최대 N명까지만 동시에 들어와도 돼."
그 이상이 되면 문을 잠그고 나머지는 대기시키는 방식입니다. 이건 세마포어(Semaphore) 로 구현되는 구조로,
동시 접근 가능한 스레드 수를 제한할 때 사용됩니다.
의문이 듭니다. 잠그는 건 이해했습니다. 그러면 최초는 어떻게 결정이 되어질까요? 단순히 눈치게임일까요?
OS 입장에서는 눈치게임이 맞습니다. 하지만, 실제로는 이 "눈치게임"이 하드웨어(CPU) 에서 통제됩니다.
즉, OS는 하드웨어가 만든 결과를 받아 "질서를 유지하는 관리자"일 뿐,
최초의 승자는 CPU가 정하는 물리적 현실에 가깝습니다.
세번째, 언어/런타임 레벨 이 레벨은 앞서 살펴본 하드웨어와 운영체제의 동기화 메커니즘을 언어를 통해 제어하는 과정을 말합니다.
우리가 작성하는 코드(synchronized, mutex, await, channel 등)는 단순한 문법이 아니라,
하드웨어의 원자적 명령과 커널의 락·스케줄링 기능을 호출하는 통로가 됩니다.
이 계층에서 언어마다 특징, 장단점, 그리고 성능 차이가 발생합니다. 예를 들어 자바는 JVM 모니터 기반의 동기화를,
Go는 고루틴과 채널을 이용한 메시지 기반 동기화를 제공합니다. 결국 언어/런타임 레벨이란,
"하드웨어의 물리적 제약을 인간이 이해할 수 있는 문법으로 표현한 층" 이라고 볼 수 있습니다.
사실 은연중에 동기화의 특징에 대해 말했습니다. 하지만 한 가지를 제외하고 2가지를 이미 말했죠. 그것은 원자성과 순서성입니다. 그렇다면 나머지 한개 가시성은 어떤것일까요?
가시성은 왜 등장하지 않은 거지?
지금까지는 두 개 이상의 스레드가 함께 동작할 때 발생하는 성질(원자성, 순서성) 에 대해 이야기했습니다.
하지만 아직 스레드 자신만의 상태에 대해서는 말하지 않았습니다. 여러 스레드가 하나의 상태로 묶이고, 올바른 순서로 정리되어도
그 변화가 실제로 보이지 않는다면 아무 의미가 없습니다. 이 "보인다"는 개념을 보장하는 것이 바로 가시성(Visibility) 입니다.
즉, 스레드가 ‘무엇을 바꿨는가’가 다른 스레드들에게도 동일하게 보이는 성질을 말합니다.
+) thread safe
열심히 프로세스와 스레드를 학습했습니다. 흐름과는 맞지 않아서 작성하지는 않았는데 스레드 세이프 하지 않는다는 표현을 많이 들립니다. 과연 어떤 뜻일까요? 여러 스레드가 동시에 같은 자원(메모리, 객체, 변수)에 접근하더라도, 프로그램의 동작이 깨지지 않고 일관된 결과를 보장할 수 있는 상태를 말합니다. 즉, "경쟁 조건(Race Condition)으로부터 안전한 코드" 라는 뜻입니다.
따라서 스레드 세이프(Thread-Safe) 하다는 것은 곧 이 세 가지 조건(원자성·순서성·가시성) 이 모두 지켜지고 있다는 뜻이며,
결국 “동기화가 완벽히 달성된 상태”를 말합니다.
결론
프로세스 부터 시작해서 스레드까지 저의 언어로 작성하면서 생각보다 많은 학습이 되었던거 같습니다. 물론, 위에서 작성한 내용들이 전부는 아닙니다. 이제 언어쪽으로 들어가서 어떻게 구현이 되었는지 살펴본다면 조금더 완벽하지 않을까요?