개발자라면 누구나 한 번은 동시성(Concurrency)과 병렬성(Parallelism)이라는 용어를 들어봤을 것입니다.
하지만 이 두 개념의 정확한 차이점을 설명하라고 하면 많은 개발자들이 머뭇거리게 됩니다.
특히 기술 면접에서 자주 등장하는 질문이기도 하죠.
이 글에서는 동시성과 병렬성의 핵심 차이점을 명확하게 설명하고, 실무에서 활용할 수 있는 예제 코드와 함께 면접에서 활용할 수 있는 답변까지 제공하겠습니다.
동시성(Concurrency)의 정의와 특징
동시성은 여러 작업을 논리적으로 동시에 처리하는 것처럼 보이게 하는 개념입니다.
실제로는 하나의 프로세서가 여러 작업 사이를 빠르게 전환하면서 마치 동시에 실행되는 것처럼 보이게 만듭니다.
이를 시분할(Time Slicing) 또는 컨텍스트 스위칭(Context Switching)이라고 부릅니다.
동시성의 핵심적인 특징
- 단일 코어에서도 구현 가능: 하나의 CPU 코어만 있어도 동시성을 구현할 수 있습니다
- 작업 전환: 여러 작업 간에 빠른 전환을 통해 동시 실행을 시뮬레이션합니다
- 논리적 동시성: 실제로는 순차적이지만 논리적으로는 동시에 실행되는 것처럼 보입니다
- 자원 공유: 같은 메모리 공간과 시스템 자원을 공유합니다
병렬성(Parallelism)의 정의와 특징
병렬성은 실제로 여러 작업을 물리적으로 동시에 처리하는 개념입니다.
여러 개의 프로세서나 코어가 각각 다른 작업을 실제로 동시에 수행합니다.
병렬성의 핵심적인 특징
- 멀티 코어 필수: 물리적으로 여러 개의 프로세서나 코어가 필요합니다
- 실제 동시 실행: 말 그대로 여러 작업이 동시에 실행됩니다
- 물리적 병렬성: 하드웨어 수준에서 실제로 병렬 처리가 일어납니다
- 독립적 실행: 각 코어가 독립적으로 작업을 처리합니다
동시성 vs 병렬성 핵심 차이점 비교
구분 | 동시성(Concurrency) | 병렬성(Parallelism) |
---|---|---|
실행 방식 | 논리적 동시 실행 | 물리적 동시 실행 |
하드웨어 요구사항 | 단일 코어 가능 | 멀티 코어 필수 |
작업 처리 | 시분할을 통한 교대 실행 | 실제 동시 처리 |
성능 향상 | 응답성 향상 | 처리량 향상 |
주요 목적 | 효율적인 자원 활용 | 계산 속도 향상 |
동시성 구현 예제 코드 (JavaScript)
JavaScript는 단일 스레드 언어이지만 이벤트 루프와 비동기 처리를 통해 동시성을 구현합니다.
// 동시성 예제: 비동기 작업들이 교대로 실행됨
async function concurrencyExample() {
console.log('작업 시작');
// 세 개의 비동기 작업을 동시에 시작
const task1 = new Promise(resolve => {
setTimeout(() => {
console.log('Task 1 완료');
resolve('결과1');
}, 2000);
});
const task2 = new Promise(resolve => {
setTimeout(() => {
console.log('Task 2 완료');
resolve('결과2');
}, 1000);
});
const task3 = new Promise(resolve => {
setTimeout(() => {
console.log('Task 3 완료');
resolve('결과3');
}, 1500);
});
// Promise.all을 사용한 동시성 처리
try {
const results = await Promise.all([task1, task2, task3]);
console.log('모든 작업 완료:', results);
} catch (error) {
console.error('작업 실패:', error);
}
}
concurrencyExample();
Node.js 동시성 예제
const fs = require('fs').promises;
async function readMultipleFiles() {
console.log('파일 읽기 시작');
try {
// 여러 파일을 동시에 읽기 (동시성)
const [file1, file2, file3] = await Promise.all([
fs.readFile('file1.txt', 'utf8'),
fs.readFile('file2.txt', 'utf8'),
fs.readFile('file3.txt', 'utf8')
]);
console.log('파일1 내용:', file1);
console.log('파일2 내용:', file2);
console.log('파일3 내용:', file3);
} catch (error) {
console.error('파일 읽기 실패:', error);
}
}
병렬성 구현 예제 코드 (Python)
Python의 multiprocessing 모듈을 사용하여 실제 병렬 처리를 구현할 수 있습니다.
import multiprocessing
import time
from concurrent.futures import ProcessPoolExecutor
def cpu_intensive_task(n):
"""CPU 집약적인 작업 (소수 계산)"""
print(f"프로세스 {multiprocessing.current_process().name}에서 작업 {n} 시작")
# 소수 찾기 (CPU 집약적 작업)
primes = []
for num in range(2, n * 1000):
for i in range(2, int(num ** 0.5) + 1):
if num % i == 0:
break
else:
primes.append(num)
print(f"작업 {n} 완료: {len(primes)}개의 소수 발견")
return len(primes)
def parallel_processing_example():
"""병렬 처리 예제"""
start_time = time.time()
# 병렬 처리를 위한 작업 목록
tasks = [10, 15, 20, 25, 30]
print("병렬 처리 시작")
# ProcessPoolExecutor를 사용한 병렬 처리
with ProcessPoolExecutor(max_workers=4) as executor:
results = list(executor.map(cpu_intensive_task, tasks))
end_time = time.time()
print(f"병렬 처리 완료: {results}")
print(f"총 실행 시간: {end_time - start_time:.2f}초")
def sequential_processing_example():
"""순차 처리 예제 (비교용)"""
start_time = time.time()
tasks = [10, 15, 20, 25, 30]
results = []
print("순차 처리 시작")
for task in tasks:
result = cpu_intensive_task(task)
results.append(result)
end_time = time.time()
print(f"순차 처리 완료: {results}")
print(f"총 실행 시간: {end_time - start_time:.2f}초")
if __name__ == "__main__":
print("=== 순차 처리 ===")
sequential_processing_example()
print("\n=== 병렬 처리 ===")
parallel_processing_example()
Java에서의 동시성과 병렬성 구현
동시성 구현: CompletableFuture 활용
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class ConcurrencyExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("동시성 예제 시작");
// 세 개의 비동기 작업을 동시에 시작
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
System.out.println("Task 1 완료");
return "결과1";
} catch (InterruptedException e) {
return "Task 1 실패";
}
});
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
System.out.println("Task 2 완료");
return "결과2";
} catch (InterruptedException e) {
return "Task 2 실패";
}
});
CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1500);
System.out.println("Task 3 완료");
return "결과3";
} catch (InterruptedException e) {
return "Task 3 실패";
}
});
// 모든 작업이 완료될 때까지 대기
CompletableFuture<Void> allTasks = CompletableFuture.allOf(task1, task2, task3);
allTasks.get();
System.out.println("모든 작업 완료");
System.out.println("결과: " + task1.get() + ", " + task2.get() + ", " + task3.get());
}
}
병렬성 구현: 병렬 스트림 활용
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
public class ParallelismExample {
public static void main(String[] args) {
System.out.println("병렬성 예제 시작");
// 큰 숫자 배열 생성
List<Integer> numbers = IntStream.rangeClosed(1, 1000000)
.boxed()
.toList();
// 순차 처리
long startTime = System.currentTimeMillis();
long sequentialSum = numbers.stream()
.mapToLong(ParallelismExample::expensiveOperation)
.sum();
long sequentialTime = System.currentTimeMillis() - startTime;
System.out.println("순차 처리 결과: " + sequentialSum);
System.out.println("순차 처리 시간: " + sequentialTime + "ms");
// 병렬 처리
startTime = System.currentTimeMillis();
long parallelSum = numbers.parallelStream()
.mapToLong(ParallelismExample::expensiveOperation)
.sum();
long parallelTime = System.currentTimeMillis() - startTime;
System.out.println("병렬 처리 결과: " + parallelSum);
System.out.println("병렬 처리 시간: " + parallelTime + "ms");
System.out.println("성능 향상: " + (double)sequentialTime / parallelTime + "배");
}
// CPU 집약적인 작업을 시뮬레이션
private static long expensiveOperation(int number) {
return IntStream.rangeClosed(1, number % 100 + 1)
.reduce(0, Integer::sum);
}
}
실무에서의 활용 사례와 선택 기준
동시성이 적합한 상황
I/O 바운드 작업에서 동시성이 효과적입니다.
- 웹 서버: 여러 클라이언트 요청을 동시에 처리
- 데이터베이스 쿼리: 여러 쿼리를 동시에 실행
- 파일 읽기/쓰기: 여러 파일 작업을 동시에 수행
- 네트워크 통신: API 호출, 외부 서비스 연동
// 실무 예제: 여러 API를 동시에 호출
async function fetchUserData(userId) {
try {
// 세 개의 다른 API를 동시에 호출 (동시성)
const [userInfo, userPosts, userFriends] = await Promise.all([
fetch(`/api/users/${userId}`).then(res => res.json()),
fetch(`/api/users/${userId}/posts`).then(res => res.json()),
fetch(`/api/users/${userId}/friends`).then(res => res.json())
]);
return {
userInfo,
userPosts,
userFriends
};
} catch (error) {
console.error('사용자 데이터 조회 실패:', error);
throw error;
}
}
병렬성이 적합한 상황
CPU 바운드 작업에서 병렬성이 효과적입니다.
- 이미지/비디오 처리: 픽셀 단위 연산, 필터 적용
- 수학적 계산: 행렬 연산, 과학 계산
- 데이터 분석: 대용량 데이터 처리, 머신러닝
- 암호화/해시: CPU 집약적인 보안 연산
# 실무 예제: 이미지 병렬 처리
from PIL import Image
import multiprocessing
from concurrent.futures import ProcessPoolExecutor
import os
def resize_image(image_path):
"""이미지 크기 조정 (CPU 집약적 작업)"""
try:
with Image.open(image_path) as img:
# 이미지 크기를 50%로 축소
new_size = (img.width // 2, img.height // 2)
resized_img = img.resize(new_size, Image.Resampling.LANCZOS)
# 새 파일명 생성
name, ext = os.path.splitext(image_path)
output_path = f"{name}_resized{ext}"
resized_img.save(output_path)
print(f"이미지 처리 완료: {output_path}")
return output_path
except Exception as e:
print(f"이미지 처리 실패 {image_path}: {e}")
return None
def process_images_parallel(image_paths):
"""이미지들을 병렬로 처리"""
print(f"총 {len(image_paths)}개 이미지를 병렬 처리합니다.")
# CPU 코어 수만큼 워커 프로세스 생성
with ProcessPoolExecutor(max_workers=multiprocessing.cpu_count()) as executor:
results = list(executor.map(resize_image, image_paths))
# 성공한 작업 수 계산
successful_results = [r for r in results if r is not None]
print(f"병렬 처리 완료: {len(successful_results)}/{len(image_paths)} 성공")
return successful_results
기술 면접에서의 완벽한 답변 전략
기본 개념 설명 답변
면접관: "동시성과 병렬성의 차이점을 설명해주세요."
모범 답변:
"동시성과 병렬성은 다음과 같은 차이가 있습니다.
동시성(Concurrency)은 여러 작업을 논리적으로 동시에 처리하는 것처럼 보이게 하는 개념입니다.
실제로는 하나의 프로세서가 여러 작업 간을 빠르게 전환하면서 동시 실행을 시뮬레이션합니다.
단일 코어에서도 구현이 가능하며, 주로 I/O 바운드 작업에서 응답성을 향상시키는 데 사용됩니다.
병렬성(Parallelism)은 여러 작업을 물리적으로 실제 동시에 처리하는 개념입니다.
여러 개의 프로세서나 코어가 각각 다른 작업을 동시에 수행하며, 멀티 코어 환경이 필수입니다.
주로 CPU 바운드 작업에서 전체 처리량을 향상시키는 데 사용됩니다.
실무에서는 웹 서버에서 여러 클라이언트 요청을 처리할 때는 동시성을, 대용량 데이터 분석이나 이미지 처리에서는 병렬성을 활용합니다."
심화 질문 대응 답변
면접관: "JavaScript는 단일 스레드인데 어떻게 동시성을 구현하나요?"
모범 답변:
"JavaScript는 단일 스레드 언어이지만 이벤트 루프(Event Loop)와 콜백 큐(Callback Queue)를 통해 동시성을 구현합니다.
비동기 작업(setTimeout, fetch, Promise 등)이 실행되면 해당 작업은 Web API나 Node.js의 libuv에서 처리되고, 완료되면 콜백이 콜백 큐에 들어갑니다.
이벤트 루프는 콜 스택이 비어있을 때 콜백 큐에서 작업을 가져와 실행합니다.
예를 들어, Promise.all()
을 사용하면 여러 비동기 작업을 동시에 시작할 수 있고, 각 작업은 백그라운드에서 처리되어 전체 실행 시간을 단축할 수 있습니다."
면접관: "실제 프로젝트에서 동시성과 병렬성을 어떻게 활용해봤나요?"
모범 답변:
"최근 프로젝트에서 두 개념을 모두 활용했습니다.
동시성 활용 사례: 사용자 대시보드에서 여러 API를 호출해야 하는 상황이 있었습니다.
사용자 정보, 주문 내역, 추천 상품을 각각 다른 API에서 가져와야 했는데, 순차적으로 호출하면 총 3초가 걸렸습니다.Promise.all()
을 사용해 동시에 요청하여 1.2초로 단축했습니다.
병렬성 활용 사례: 데이터 분석 파이프라인에서 대용량 CSV 파일을 처리할 때 Python의 multiprocessing
을 사용했습니다.
단일 프로세스로 처리하면 30분 걸리던 작업을 4개 프로세스로 병렬 처리하여 8분으로 단축했습니다.
선택 기준은 작업의 특성입니다. I/O 대기가 많으면 동시성을, CPU 집약적 계산이 많으면 병렬성을 선택합니다."
성능 최적화와 주의사항
동시성 구현 시 주의사항
경쟁 상태(Race Condition) 방지가 중요합니다.
// 잘못된 예제: 경쟁 상태 발생 가능
let counter = 0;
async function incrementCounter() {
const currentValue = counter;
await new Promise(resolve => setTimeout(resolve, 10)); // 비동기 작업 시뮬레이션
counter = currentValue + 1;
}
// 여러 번 동시 실행하면 예상과 다른 결과가 나올 수 있음
Promise.all([
incrementCounter(),
incrementCounter(),
incrementCounter()
]);
// 올바른 예제: 뮤텍스나 원자적 연산 사용
class SafeCounter {
constructor() {
this.counter = 0;
this.mutex = Promise.resolve();
}
async increment() {
this.mutex = this.mutex.then(async () => {
const currentValue = this.counter;
await new Promise(resolve => setTimeout(resolve, 10));
this.counter = currentValue + 1;
});
return this.mutex;
}
getValue() {
return this.counter;
}
}
병렬성 구현 시 주의사항
메모리 사용량과 컨텍스트 스위칭 비용을 고려해야 합니다.
# 최적의 워커 수 결정
import multiprocessing
import psutil
def get_optimal_worker_count():
"""최적의 워커 프로세스 수 계산"""
cpu_count = multiprocessing.cpu_count()
memory_gb = psutil.virtual_memory().total / (1024**3)
# CPU 바운드 작업: CPU 코어 수와 동일
# I/O 바운드 작업: CPU 코어 수의 2-4배
# 메모리 제약 고려
optimal_count = min(cpu_count, int(memory_gb / 2)) # 2GB per worker 가정
return max(1, optimal_count)
# 사용 예제
optimal_workers = get_optimal_worker_count()
print(f"권장 워커 수: {optimal_workers}")
최신 기술 동향과 발전 방향
비동기 프로그래밍의 진화
Async/Await 패턴이 현대적인 비동기 프로그래밍의 표준이 되었습니다.
// 과거: 콜백 헬
function fetchUserData(userId, callback) {
fetch(`/api/users/${userId}`)
.then(response => response.json())
.then(user => {
fetch(`/api/users/${userId}/posts`)
.then(response => response.json())
.then(posts => {
fetch(`/api/users/${userId}/friends`)
.then(response => response.json())
.then(friends => {
callback(null, { user, posts, friends });
})
.catch(callback);
})
.catch(callback);
})
.catch(callback);
}
// 현재: async/await로 개선
async function fetchUserDataModern(userId) {
try {
const [userResponse, postsResponse, friendsResponse] = await Promise.all([
fetch(`/api/users/${userId}`),
fetch(`/api/users/${userId}/posts`),
fetch(`/api/users/${userId}/friends`)
]);
const [user, posts, friends] = await Promise.all([
userResponse.json(),
postsResponse.json(),
friendsResponse.json()
]);
return { user, posts, friends };
} catch (error) {
console.error('사용자 데이터 조회 실패:', error);
throw error;
}
}
마이크로서비스와 분산 시스템
현대적인 애플리케이션에서는 마이크로서비스 아키텍처를 통해 시스템 전체의 동시성과 병렬성을 구현합니다.
# Docker Compose를 통한 서비스 병렬 실행 예제
version: '3.8'
services:
user-service:
build: ./user-service
ports:
- "3001:3000"
environment:
- NODE_ENV=production
order-service:
build: ./order-service
ports:
- "3002:3000"
environment:
- NODE_ENV=production
notification-service:
build: ./notification-service
ports:
- "3003:3000"
environment:
- NODE_ENV=production
redis:
image: redis:alpine
ports:
- "6379:6379"
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
depends_on:
- user-service
- order-service
- notification-service
요약 및 핵심 포인트
동시성과 병렬성은 현대 소프트웨어 개발에서 성능 최적화의 핵심 개념입니다.
동시성은 단일 코어에서도 구현 가능한 논리적 동시 실행으로, I/O 바운드 작업에서 응답성을 향상시킵니다.
병렬성은 멀티 코어 환경에서의 물리적 동시 실행으로, CPU 바운드 작업에서 처리량을 향상시킵니다.
실무에서는 작업의 특성을 정확히 파악하여 적절한 접근 방식을 선택하는 것이 중요합니다.
기술 면접에서는 개념의 정확한 이해와 함께 실무 경험을 바탕으로 한 구체적인 예시를 제시할 수 있어야 합니다.
앞으로는 WebAssembly, WebWorkers, Serverless Architecture 등 새로운 기술들이 동시성과 병렬성 구현에 더 많은 선택지를 제공할 것으로 예상됩니다.
개발자라면 이러한 기본 개념을 확실히 이해하고, 지속적으로 발전하는 기술 동향을 따라가며 실무에 적용할 수 있는 능력을 기르는 것이 중요합니다.
'컴퓨터 과학(CS)' 카테고리의 다른 글
캐시 일관성(Cache Coherency)과 멀티코어 CPU 구조의 완벽 이해 (0) | 2025.05.23 |
---|---|
OS 스케줄링 알고리즘 종류 및 작동 방식 완벽 가이드 (0) | 2025.05.23 |
메모리 계층 구조 이해: 레지스터, 캐시, RAM, 디스크 차이 (0) | 2025.05.18 |
해시(Hash) 함수와 충돌 해결 방법 – CS 면접 대비 실전 예제 (2) | 2025.05.18 |
면접에서 자주 나오는 동기화 이슈 – 스레드 안전성과 자바 코드로 설명하기 (4) | 2025.05.13 |