💻 프로그래밍

Concurrency

동시성

여러 작업을 동시에 처리하는 개념. 병렬성과 구분. 스레드, 코루틴으로 구현.

📖 상세 설명

동시성(Concurrency)은 여러 작업이 논리적으로 동시에 진행되는 것처럼 처리하는 프로그래밍 패러다임입니다. 실제로 한 시점에 하나의 작업만 실행되더라도, 작업 간 빠른 전환(context switching)을 통해 동시에 여러 작업이 진행되는 것처럼 보이게 합니다.

동시성과 병렬성(Parallelism)은 자주 혼동되지만 명확히 다른 개념입니다. 동시성은 "여러 작업을 다루는 것"에 관한 것이고, 병렬성은 "여러 작업을 동시에 실행하는 것"입니다. 싱글 코어 CPU에서도 동시성은 가능하지만, 병렬성은 멀티 코어에서만 실현됩니다.

동시성은 스레드(Thread), 프로세스(Process), 코루틴(Coroutine), 이벤트 루프 등 다양한 방식으로 구현됩니다. 스레드는 OS 레벨에서 관리되어 무겁지만 진정한 병렬 실행이 가능하고, 코루틴은 사용자 레벨에서 협력적으로 스케줄링되어 가볍고 효율적입니다.

실무에서 동시성은 웹 서버의 다중 요청 처리, 비동기 I/O 작업, GUI 애플리케이션의 반응성 유지, 백그라운드 작업 실행 등에 핵심적으로 사용됩니다. Python의 asyncio, JavaScript의 Promise, Go의 고루틴, Java의 Virtual Thread 등 현대 언어들은 모두 강력한 동시성 지원을 제공합니다.

💻 코드 예제

# Python asyncio를 활용한 동시성 처리 예제
import asyncio
import aiohttp
import time

# 1. 기본 코루틴과 동시성
async def fetch_url(session, url):
    """비동기적으로 URL에서 데이터 가져오기"""
    async with session.get(url) as response:
        data = await response.text()
        print(f"완료: {url[:50]}... ({len(data)} bytes)")
        return data

async def main():
    urls = [
        "https://api.github.com",
        "https://httpbin.org/get",
        "https://jsonplaceholder.typicode.com/posts/1"
    ]

    start = time.time()

    async with aiohttp.ClientSession() as session:
        # 동시에 모든 요청 실행 (병렬 X, 동시성 O)
        tasks = [fetch_url(session, url) for url in urls]
        results = await asyncio.gather(*tasks)

    elapsed = time.time() - start
    print(f"\n총 소요 시간: {elapsed:.2f}초 (동시 처리)")
    print(f"순차 처리였다면 약 {len(urls) * 0.3:.2f}초 이상 소요")

# 2. 동시성 vs 순차 처리 비교
async def io_bound_task(name, delay):
    """I/O 바운드 작업 시뮬레이션"""
    print(f"[{name}] 시작...")
    await asyncio.sleep(delay)  # 비동기 대기 (다른 작업에 양보)
    print(f"[{name}] 완료! ({delay}초)")
    return name

async def concurrent_demo():
    """동시성 데모: 3개 작업이 동시에 진행"""
    print("=== 동시성 처리 ===")
    start = time.time()

    # 동시에 실행 - 총 2초 소요 (가장 긴 작업 기준)
    results = await asyncio.gather(
        io_bound_task("작업A", 2),
        io_bound_task("작업B", 1),
        io_bound_task("작업C", 1.5)
    )

    print(f"결과: {results}")
    print(f"소요 시간: {time.time() - start:.2f}초\n")

# 실행
asyncio.run(main())
asyncio.run(concurrent_demo())

🗣️ 실무에서 이렇게 말하세요

💬 회의에서
"이 API 게이트웨이는 동시성 처리가 핵심입니다. 현재 초당 5,000개 요청을 처리해야 하는데, asyncio 기반으로 구현하면 스레드 풀보다 메모리 효율이 10배 이상 좋고, 컨텍스트 스위칭 오버헤드도 줄일 수 있습니다."
💬 면접에서
"동시성과 병렬성의 차이를 설명드리면, 동시성은 하나의 바리스타가 여러 주문을 번갈아 처리하는 것이고, 병렬성은 여러 바리스타가 각자 주문을 동시에 처리하는 것입니다. I/O 바운드 작업에는 동시성이, CPU 바운드 작업에는 병렬성이 효과적입니다."
💬 코드 리뷰에서
"여기서 asyncio.gather 대신 asyncio.create_task로 개별 태스크를 만들면 에러 핸들링이 더 유연해집니다. 그리고 세마포어로 동시 실행 개수를 제한하지 않으면 외부 API rate limit에 걸릴 수 있어요."

⚠️ 흔한 실수 & 주의사항

Race Condition (경쟁 상태)

여러 작업이 공유 자원에 동시에 접근할 때 발생합니다. 뮤텍스, 세마포어, Lock 등의 동기화 메커니즘을 사용하거나, 가능하면 불변 데이터 구조를 활용하세요.

Deadlock (교착 상태)

두 개 이상의 작업이 서로의 자원을 기다리며 무한 대기하는 상태입니다. Lock 획득 순서를 일관되게 유지하고, 타임아웃을 설정하여 예방하세요.

동시성과 병렬성 혼동

Python의 GIL(Global Interpreter Lock)로 인해 threading은 CPU 바운드 작업에서 실제 병렬 실행이 안됩니다. CPU 작업은 multiprocessing을, I/O 작업은 asyncio를 사용하세요.

적절한 동시성 패턴 선택

I/O 바운드는 async/await, CPU 바운드는 멀티프로세싱, 고성능이 필요하면 Go의 고루틴이나 Rust의 tokio를 고려하세요. 상황에 맞는 도구 선택이 중요합니다.

🔗 관련 용어

📚 더 배우기