
공짜 점심은 끝났어 -Herb Sutter
갑자기 뜬금없이 무료 급식은 끝났다니 동네 노숙자들은 다 죽으라는 소리인가, 이 소리는 2000년대 중반 우리는 *무어의 법칙에 따라 CPU 성능이 발전했지만
CPU 클럭 속도(Clock Speed)가 물리적 한계(발열,전력 소모)등으로 인해서 싱글 코어의 성능의 향상이 멈추게 되었다
개발자들은 이제 컴퓨터의 성능을 다 이끌어 나갈 새로운 방법을 찾아야했고, 좋았던 시절이 끝났다며 Herb Sutter가 05년도에 글을 작성했다.
이제 프로그래머는 안락하고 따뜻한 싱글 코어와 품속에서 벗어나 차가운 병렬 프로그래밍을 사용하여야 하드웨어의 잠재력을 다 발휘할 수 있겠되었다.
그리고 이때 수많은 책들이 난립하고, 정리되는 중 병렬 프로그래밍 계의 명저이자 불후의 고전인 책이 나온다.
*무어의 법칙-CPU 성능이 2년마다 2배씩 증가한다던 법칙(초기에는 1년 이후에 수정해서 2년, 이후에 인텔에서 18개월이 되었다)

The Art of Concurrency
쓰레드 몽키라는 부제처럼 초보자도 이해하기 쉽게 친절하게 설명해주고, 이제 나는 이 책의 내용을 바탕으로 이야기할 것이다.
물론 이 책은 꽤 오래된 책이기에 최신 개념의 이야기는 적용되어있지 않다.
Rust의 소유권이나, GO의 Goroutine이라거나 이런 내용은 없지만 역시 핵심 내용은 여전히 유지된다.
그리고 초창기 프로그래머가 겪은 교훈을 탐구해보자.

너무 깊게 설명하면, 그것만 설명해도 모자라기때문에 필요한 개념만 간단하게 설명하겠다
1.프로세스(Process):운영체제의 자원 할당 및 관리 기본 단위
-실행중인 프로그램의 인스턴스. 자원 할당 및 스케쥴링의 기본 단위
-독립적인 메모리 공간을 차지함
2.쓰레드(Thread):프로세스내에서 실제 작업을 수행하는 실행 흐름의 최소단위
-쓰레드는 프로세스 내에서 실제 코드를 실행하는 실행흐름의 최소단위 , 하나의 프로세스는 하나이상의 쓰레드를 가질 수 있다.
-프로세스 내의 모든 쓰레드는 프로세스 자원(주소 공간, 메모리,파일 디스크립터)을 공유함
3.스케쥴링(Scheduling):운영체제가 어떤 프로세스 또는 쓰레드에게 CPU를 할당할지 결정하는 과정
-컨텍스트 스위칭은 스레드 교체시 발생하는 오버헤드.
-False Sharing: 서로 다른 쓰레드가 '논리적'으로는 독립된 데이터를 쓰지만 '물리적으로는' 같은 캐시 라인에 위치하여 발생하는 성능저하 현상. (논리적 독립, 물리적 공유)
그럼
비유를 들겠다.
프로세스는 공장
쓰레드는 공장 안에 있는 노동자(마치 글쓴이와 같은)
스케쥴링은 공장안에 있는 노동자가 받는 공장안의 작업 지침이다.
False Sharing은 작업자(쓰레드)들이 같은 도구상자(캐시라인)을 두고 다투는 현상이라고 할 수 있다.
이제 병렬 프로그래밍을 할 수 있는 기초에 섰다.
병렬성과 동시성: 차이점은 무엇인가?

"병렬(parallel)"과 "동시(concurrent)"라는 용어는 멀티코어 프로세서가 일반화된 이후 더욱 빈번하게 사용되고 있다.
하지만, 이러한 용어들은 멀티코어가 등장하기 전부터 컴퓨팅 분야에서 혼란스럽게 사용되기도 했다.
그렇다면 이 두 개념은 어떻게 다르며, 서로 구별할 수 있을까? 아니면 두 용어가 같은 의미를 가지고 있는 걸까?
동시성(Concurrency)과 병렬성(Parallelism)의 차이 구분해보자

동시성(Concurrency)
-여러개의 작업이 동시에 '진행 중(in progress)'일 수 있는 시스템을 의미한다.
-한 개의 CPU 코어에서도 동시성을 구현할 수 있다.
-완전히 동시에 실행되는 것이 아니라, 여러개의 작업이 '진행 중(in progress)'에 상태에 있는 것이 핵심이다.

병렬성(Parallelism)
-두 개이상의 작업이 동시에 "실행(executin)'되는 시스템을 의미한다.
-병렬성을 구현하려면 반드시 여러개의 CPU 코어가 필요하다
-즉 실제로 동시에 실행(Simulataneous Execution)되어야 병렬성이 성립한다.
결국 핵심 차이점은 다음과 같다
-동시성은 여러개의 작업이 '진행중'이지만 반드시 동시에 실행될 필요는 없다, 단일 코어로도 가능
-병렬성은 여러개의 작업이 '물리적'으로 동시에 실행된다. 여러개의 CPU 코어가 필요하다
즉 모든 병렬 프로그래밍은 동시성을 갖지만, 모든 동시 프로그래밍이 병렬적인 것은 아니다.
이 내용을 요약하여 보자.
개념 | 병렬 프로그래밍 (Parallel) | 동시 프로그래밍 (Concurrent) |
---|
정의 | 여러 작업이 실제로 동시에 실행됨 | 여러 작업이 진행 중이지만, 실제로 동시에 실행되지 않을 수도 있음 |
CPU 코어 활용 | 멀티코어 / 멀티 프로세서 필요 | 싱글코어에서도 가능 |
중요한 요소 | 작업 분할, 로드 밸런싱, 데이터 의존성 제거 | 태스크 스케줄링, 리소스 동기화, 상태 관리 |
예제 | 대규모 행렬 연산, 머신러닝 병렬 처리 | 웹 서버의 여러 요청 처리, UI 이벤트 루프 |
자 이제 동시성 프로그래밍과 병렬성을 구분했다.
이제 무엇을 해야할까?
가장 먼저 해야할 일은 무엇일까?
'언제 사용해야할 것인가?'

브룩스의 법칙 : "늦은 프로젝트에 인력을 추가하면 더 늦어지게 된다"
언제 동시성을 적용할 것인가?에 대한 대답을 하기위해서 우리 위대한 선배 프로그래머는 하나의 논리 단위를 만들어 둔다
그것이 바로 'TASK'다
역시 TASK의 역사를 읊으면 끝이 없지만, 대충 설명하자면 TASK 는 이전보다 훨씬 예전에 생겼었지만 이전에는 쓰레드와 유사한 의미로 쓰이다가
00년대 후반에 이르러 추상화된 논리 단위로 쓰이기 시작한다.
TASK라는 논리단위는 복잡성을 효과적으로 관리하고, 개발자는 아키텍쳐라는 관점으로 보면서 동시성 적용 여부를 판단할 수 있게 한다.
TASK를 사용하면 Lowlevel 쓰레드 관리나 동기화에 대한 세부적 고민에서 벗어나
'어떤 작업을 병렬로 처리해야하는가' 같은 고수준의 설계 결정에 집중할 수 있게 한다.
TASK 기반 동시성 모델은 다음과 같은 틀을 프로그래머에게 제공해준다.
1. TASK 분할(Task Decomposition): 전체 프로그램을 작고 독립적인 Task들로 분할한다. Task는 명확한 입력과 출력을 가지고, 특정기능을 수행하는 논리적 단위
2. TASK 특성 분석(Task Characterization): Task의 특성을 분석하여, 어떤 Task는 병렬처리에 적합하고, 어떤 Task는 비동기 처리를 할지 결정에 도움을 준다.
3. Task 스케쥴링 및 실행(Task Scheduling and Excution) Task 스케쥴러를 사용하여 Task를 효율적으로 배분하고 실행한다. Task 의존관계를 고려하고 실행 순서를 정한 후, 자원을 효율적으로 배분한다.
어쨌건 TASK 모델로 분석했을때
동시성을 사용해야할때는
I/O 바운드 작업- 네트워크 통신,파일 입출력/데이터 베이스 쿼리 등 I/O 작업
CPU 바운드(CPU-Bound): 복잡한 계산,이미지 처리, CPU연산에 최대한 활용하여 계산 작업을 분산처리
응답성이 중요한 프로그램:GUI같이 사용자 경험이 중요한 프로그램등이 예시이다
동시성을 사용하지 말아야할때는
싱글 스레드:프로그램 자체가 본질적으로 싱글 스레드로 설계되있어서 동시성 적용이 어렵다.
작업량이 매우 적은 프로그램:전체 실행 시간이 짧을 경우 오히려 쓰레드 생성 소멸 자체가 오버헤드가 될 수 있다.
하드웨어 자원이 제한적인 경우: 특정 수준의 저수준 임베디드.
하지만 The Art of Concurrency에서는 실전중심의 접근 방식을 배치하여, 저수준 동기화 매커니즘보다는 일종의 상위 추상화 도구인 OpenMP,TBB에 집중한다

컴퓨터 과학은 컴퓨터에 관한 것이 아닌다. 마치 천문학이 망원경에 관한 학문이 아닌 것처럼.
에츠허르 데이크스트라(Edsger Dijkstra)가 말했듯이 설계의 본질은 도구가 아닌 사고방식이다.
TASK설계는 본질은 도구가 아닌 사고방식이고, 아래는 이 TASK가 사용할 도구다.
실제로 책은 Mutex를 직접 흐름보다는 개념적 설명을 우선시하기때문에
이 부분부터는 책의 내용을 제외하고 동시성을 구현하기 위한 '도구' 즉 시스템을 이야기할 차례다.

쓰레드&락(Threads and Locks)
핵심특징: 가장 전통적 동시성 모델이며, 운영체제가 제공하는 '쓰레드' 라는 실행 단위와
뮤텍스Mutex , 세마포어Semaphore 와 같은 락(Lock) 기반 매커니즘을 사용하여 동시성을 구현한다.
공유 메모리 기반으로 쓰레드 간 데이터 공유가 용이하지만, 동기화 문제(레이스 컨디션, 데드 락)등 발생 가능성이 높고, 프로그래머가 직접 락을 관리해야하므로 복잡성이 높다.
대부분의 언어 C, C++,Java,C#등이 지원한다.
TASK와 관계:쓰레드와 락 모델은 가장 Low-Level이며, 기본적인 병행성이다.
Task는 쓰레드 풀이라는 쓰레드 기반 모델을 추상화하는데, Task를 쓰레드위에서 '구현'한다.

CSP(CommunicatingSequential Processes)
핵심특징: 순차적으로 실행되는 프로세스 간의 통신을 동시성으로 구현하는 모델
프로세스 (또는 쓰레드) 는 독립적인 실행 단위로 동작하며, 데이터 공유 대신 "채널 (Channel)" 이라는 메시지 큐를 통해 데이터를 주고받으며 동기화
채널은 쓰레드 간 안전한 통신을 보장하고, 데이터 경쟁 (Race Condition) 을 원천적으로 방지
고 (Go), Erlang, Clojure (core.async 라이브러리) 등에서 주로 사용
TASK와의 관계: 일반적으로 생산자와 소비자의 관계로 구현된다. CSP 채널은 태스크 작업의 큐로 활용되고, TASK는 풀로 동적워커 그룹을 관리한다.
TASK는 실행의 단위의 추상화고
CSP 채널은 통신의 규칙을 추상화한다. 이 둘의 결합은 공유 메모리 없는 동시성이라는 CSP철학을 구현하기에 좋다.

Actor모델
핵심 특징: "액터 (Actor)" 라는 독립적인 객체들이 메시지를 통해 상호작용하는 분산 병행 모델
액터는 자신만의 상태와 행동을 캡슐화하고, 메시지 큐를 통해 비동기적으로 다른 액터와 통신 액터 모델은 inherently 병행적이며,
메시지 기반 통신을 통해 동기화와 데이터 공유 문제를 간결하고 안전하게 해결할 수 있도록 도와줌. 에를랑 (Erlang), 아카 (Akka), 오를레앙 (Orleans), Vert.x 등
액터 모델을 기반으로 하는 다양한 프레임워크가 존재하며, 분산 시스템, 내결함성 시스템, 반응형 시스템 구축에 널리 사용됨
TASK와의 관계; 액터 모델과 TASK는 동시성 모델을 추상화하는 수준에서 유사점을 가지나,
액터는 독립적인 행동자, TASK는 독립적인 단위로 추상화되어있다. 일반적으로 Task+액터 모델은 메세지 주도 아키텍쳐로 TASK를 액터의 메세지 처리 단위로 사용한다.

STM(SoftWare Transactional Memory)
핵심 특징: 데이터베이스 트랜잭션 개념을 메모리 연산에 적용하여 락 없이 동기화를 구현하는 모델
원자적 블록 (Atomic Block) 을 사용하여 공유 메모리 접근을 트랜잭션으로 감싸고, 트랜잭션 충돌이 발생하면 자동으로 롤백 (Rollback) 하여 데이터 일관성을 보장한다.
락 기반 동기화의 복잡성과 데드락 문제를 해결하고, 더욱 쉽고 안전하게 동시성 제어를 할 수 있도록 도와줌
하스켈 (Haskell), 클로저 (Clojure), 스칼라 (Scala), Java (STM 라이브러리) 등에서 지원하며, 복잡한 공유 상태 관리가 필요한 병행 프로그램에 유용하다.
TASK와의 관계: Task는 트랙잭션 작업의 비동기 실행 단위가 되고, STM은 공유 메모리에 대한 원자적(Atomic) 접근을 보장한다. 장점은 트랙잭션의 합성성(Composability)가 보장된다는 것이다.

"병렬 컴퓨팅이 미래다"라는 말은 완전히 엉터리다 -리누스 토르발즈
나 역시 전설적인 프로그래머인 리누스 형님의 말을 이해한다.
이런 어려운 프로그래밍이 프로그래머의 미래가 되어서는 안된다.
만약 병렬 프로그래밍을 기본으로 해야할 세상이 온다면 나는 삶의 희망을 잃고 내 방 천장에 키링이 될 것이다.
위 내용들을 단순하게 짧게 요약하면 다음과 같다.
동시 프로그래밍은
1.Task 스케쥴링(Task Scheduling) -어떤 작업을 언제 실행할 것인가?
2.상호 배제(Mutex),락(Lock),비동기처리(Async)등 동기화 기법
3.리소스 경합(Resource Contention) 최소화-여러 작업이 동시에 공유 작업에 접근할때 해결
-동시 프로그래밍은 어떤 순서로 실행할 것인가?의 문제다
병렬 프로그래밍은
1.Task 분할(Work Decomposition)
2.데이터 종속성(Data Dependency) 최소화
3. 로드 밸런싱(Load Balancing)-모든 코어가 균등하게 작업하게 하는 것
-병렬 프로그래밍은 어떻게 나눌 것인가?의 문제다
그럼 다들 오늘도 즐거운 프로그래밍!
댓글 영역
획득법
① NFT 발행
작성한 게시물을 NFT로 발행하면 일주일 동안 사용할 수 있습니다. (최초 1회)
② NFT 구매
다른 이용자의 NFT를 구매하면 한 달 동안 사용할 수 있습니다. (구매 시마다 갱신)
사용법
디시콘에서지갑연결시 바로 사용 가능합니다.