기본 콘텐츠로 건너뛰기

라벨이 asyncio인 게시물 표시

Shell Returncodes: -N vs 128+N

`shell=True`인데 returncode가 음수가 아니라서 당황했다면: PR #146255가 정리한 “셸의 책임” meta_description: POSIX에서 subprocess의 returncode는 보통 신호 종료면 -N이라고 배웠지만, shell=True 나 asyncio.create_subprocess_shell() 에서는 그 규칙이 깨질 수 있다. CPython PR #146255는 “returncode는 셸의 종료 상태를 반영하며, 신호를 128+N 같은 코드로 매핑할 수 있다”는 점을 문서로 명확히 했다. 운영 코드에서 재현/로깅/알람을 어떻게 설계해야 덜 흔들리는지 정리한다. meta_keywords: subprocess, asyncio, returncode, shell=True, create_subprocess_shell, create_subprocess_exec, POSIX, signal, 128+N, Bash, exit status, SIGTERM, SIGKILL, 프로세스, 종료코드, 운영, 재현, 로깅, 알람, 파이썬 meta_robots: index,follow 운영하다 보면 이 상황을 한 번은 만난다. 프로세스가 SIGTERM으로 죽었으니 returncode == -15 일 거라고 생각했는데 실제 로그에는 143 이 찍혀 있다(= 128 + 15) 어떤 경우는 또 -15 로 찍힌다 그래서 대시보드가 갈라지고, “이번 장애는 신호 종료냐? 정상 종료냐?” 분류가 흔들린다. CPython PR #146255는 이 혼란의 원인을 문서로 깔끔하게 정리한다. 핵심은 한 줄이다. shell=True 면 returncode는 ‘자식 프로세스’가 아니라 ‘셸(/bin/sh)의 종료 상태’를 반영한다. 셸은 신호를 그대로 “음수”로 내보내지 않을 수 있고, 대신 128+N 같은 규칙으로 매핑할 수 있다(참고자료). 1) PR #146255가 실제로 추가한 문장(요약) diff를...

Python에서 asyncio 완전 정복 (await, async, gather 등)

어휴, 요즘 파이썬으로 비동기 프로그래밍 하는 재미에 푹 빠졌어요! 특히 asyncio 는 정말 마법 같더라고요. 처음엔 좀 낯설었는데, 익숙해지니까 속도 향상이 눈에 띄게 느껴져서 완전 반해버렸습니다. 이 글에선 제가 asyncio 를 배우면서 깨달은 점들을 풀어놓을게요. 혹시 비동기 프로그래밍이 뭔지 잘 모르시겠다면, 간단히 말해 여러 작업을 동시에 처리해서 프로그램 속도를 엄청나게 높이는 기술이라고 생각하시면 돼요. 마치 여러 요리사가 동시에 음식을 만들어서 손님에게 빨리 제공하는 것과 비슷하죠! 일단 async 와 await 라는 녀석들이 핵심인데요, async 는 함수 앞에 붙여서 "얘는 비동기 함수야!"라고 선언하는 거예요. 그리고 await 는 다른 비동기 함수가 끝날 때까지 기다리라고 지시하는 역할을 하죠. 예를 들어, 네트워크에서 데이터를 가져오는 함수가 있다면, await 를 사용해서 데이터가 다 가져올 때까지 기다렸다가 다음 작업을 진행할 수 있어요. 그 동안 다른 작업을 처리할 수 있으니, 마치 멀티태스킹을 하는 것처럼 느껴져요. 신기하지 않나요? 그리고 asyncio.gather 는 여러 비동기 함수를 동시에 실행하고 결과를 모아주는 아주 유용한 친구입니다. 제가 웹사이트 여러 개에서 데이터를 동시에 가져와야 할 때 정말 요긴하게 썼어요. 하나씩 순서대로 가져오는 것보다 훨씬 빠르더라고요! 마치 여러 개의 탭을 동시에 열어놓고 작업하는 것과 같다고 생각하시면 될 것 같아요. 실제로 제가 썼던 코드를 보여드릴게요. 세 개의 웹사이트에서 데이터를 가져오는 예제인데요. (아래 코드 삽입) 이 코드를 보시면, fetch_data 함수가 각 웹사이트에서 데이터를 가져오는 역할을 하고, asyncio.gather 가 이 함수들을 동시에 실행하도록 도와주는 것을 볼 수 있을 거예요. asyncio.sleep(2) 는 네트워크 지연을 시뮬레이션하기 위해 넣...

Python의 GIL(Global Interpreter Lock) 개념과 멀티스레딩 한계

개요 Python의 GIL(Global Interpreter Lock)은 한 번에 하나의 스레드만 Python 인터프리터에 접근할 수 있도록 제한하는 뮤텍스입니다. 멀티코어 CPU 환경에서 병렬 처리를 기대하며 멀티스레딩을 사용하면 오히려 성능 저하를 경험하게 되는 주요 원인 중 하나입니다. 이 글에서는 GIL의 개념, 멀티스레딩에 미치는 영향, 그리고 성능 개선을 위한 실용적인 대안을 살펴보겠습니다. 특히 I/O-bound 작업과 CPU-bound 작업에 대한 차이점을 명확히 이해하는 것이 중요합니다. 핵심 개념 정리 GIL은 Python 인터프리터 내부의 데이터 구조(예: 객체, 메모리)에 대한 동시 접근으로 인한 데이터 손상을 방지하기 위해 존재합니다. 단일 스레드에서만 Python 인터프리터를 사용할 수 있도록 하여 안전성을 보장하지만, 멀티코어 CPU의 장점을 활용하지 못하게 만드는 단점이 있습니다. 즉, 여러 스레드가 동시에 실행되는 것처럼 보이지만 실제로는 스레드들이 GIL을 얻기 위해 순차적으로 실행되기 때문에 CPU 코어를 효율적으로 사용하지 못합니다. 이러한 제약은 CPU-bound 작업(연산 집약적인 작업)에서 특히 심각한 성능 저하를 야기합니다. 실전 코드 예제 다음은 GIL의 영향을 보여주는 간단한 예제입니다. CPU-bound 작업(소수 계산)을 수행하는 두 개의 스레드를 생성하고, 실행 시간을 측정합니다. import threading import time import math def cpu_bound_task(n): result = math.factorial(n) # CPU-bound 작업 if __name__ == "__main__": start_time = time.time() threads = [] for i in range(2): thread = threading.Thread(target=cpu_bou...