GDSC SungShin Women's University 23-24/Session

[3월 정기세션] 비동기와 동시성 & 블록과 논블록(with Python 비동기 프로그래밍 실습

GDSC SungShin Team 2024. 5. 13. 19:21

 

안녕하세요! GDSC Sungshin 교육팀 팀장 최애림입니다 :)

 

 2024년 3월 정기세션에서 교육팀은 '비동기와 동시성 & 블록과 논블록'이라는 주제로 교육을 진행했습니다.

 

추가적으로 파이썬으로비동기 프로그래밍 실습을 진행했습니다.

 

 

목차는

1. 동기 vs 비동기

2. 블로킹 vs 논블로킹

3. 동시성 vs 병렬성

4. 싱글스레드 vs 멀티스레드

5. 파이썬에서 Global Interpreter Lock(GIL)

6. 파이썬으로 비동기 프로그래밍(코루틴) 실습(with FAST API)

입니다.

 

 

Node.js에서는 비동기 I/O를 지원하며 논블로킹 싱글스레드를 지원한다고 하는데

여기서 나오는 비동기I/O지원, 논블로킹 싱글스레드가 무엇일까요?

같이 이러한 개념들에 대해 알아보겠습니다!

 

 

코드가 동기적으로 동작한다는 코드가 반드시 작성된 순서대로 작동됨을 의미합니다.
이는 서버에서 요청을 보냈을 때 응답이 돌아와야 다음 동작을 수행할 수 있다는 것입니다.

즉, A작업이 모두 진행 될때까지 B작업은 대기해야합니다.

 

앞에 보이는 카페를 예시로 들자면 한명의 직원이 주문을 받고 커피를 직접 만들어 고객에게 전해준 이후에 그 다음 손님을 받을 수 있음을 보여줍니다.

 

 

코드가 비동기적으로 동작한다는 것은 코드가 반드시 작성된 대로 동작하는 것이 아님을 의미합니다.
요청을 보냈을 때 응답 상태와 상관없이 다음 동작을 수행 할 수 있습니다. 즉 A작업이 시작하면 동시에 B작업이 실행됩니다. A작업은 결과값이 나오는대로 출력됩니다.


아까와 같은 카페를 예시로 들면 주문을 받는 직원이 고객에게 진동벨을 주고 진동벨이 울리면 다른 직원이 만든 자신의 음료를 받으면 됩니다. 이때 주문 순서와 상관없이 상대적으로 빨리 만들어지는 음료는 먼저 나올 수 있습니다.

 

 

동기와 비동기의 차이를 비교하자면 다음과 같습니다


동기방식은 설계가 매우 간단하고 직관적이지만 결과가 주어질 때까지 아무것도 못하고 대기해야 하는 단점이 있고,
비동기방식은 동기보다 복잡하지만 결과가 주어지는데 시간이 걸리더라도 그 시간 동안 작업을 할 수 있으므로 자원을 효율적으로 사용할 수 있는 장점이 있습니다.


둘 중에 뭐가 더 낫다라고 하는 것보다 상황에 따라 적절하게 선택하면 됩니다.

 

 

블로킹과 논블로킹은 A 함수가 B 함수를 호출했을 때, 제어권을 어떻게 처리하느냐에 따라 달라집니다
블로킹은 A 함수가 B 함수를 호출하면, 제어권을 A가 호출한 B 함수에 넘겨줍니다

앞의 그림을 보면서 따라가자면
먼저 A함수가 B함수를 호출하면 B에게 제어권을 넘깁니다.
제어권을 넘겨받은 B는 열심히 함수를 실행합니다. A는 B에게 제어권을 넘겨주었기 때문에 함수 실행을 잠시 멈추게 됩니다. B함수는 실행이 끝나면 자신을 호출한 A에게 제어권을 돌려주고 A가 다시 실행되게 됩니다.

 

 

논블로킹은 A함수가 B함수를 호출해도 제어권은 그대로 자신이 가지고 있는 것을 의미합니다.
그림과 함께 보면 A함수가 B함수를 호출하면, B 함수는 실행되지만, 제어권은 A 함수가 그대로 가지고 있게 됩니다.
A함수는 계속 제어권을 가지고 있기 때문에 B함수를 호출한 이후에도 자신의 코드를 계속 실행합니다.

 

 

블로킹과 논블로킹를 들으면서 '블로킹이 동기, 논블로킹이 비동기 아니야?' 라고 생각하실 수 있습니다.

하지만 이 둘은 다른 의미입니다


블로킹과 논블로킹은  현재의 작업 상태에 따라 동작이 결정되는 것이고, 동기와 비동기는 결과를 기다리는 주체가 누구인지에 따라 결정됩니다.


블로킹과 논블로킹의 차이점은 다음과 같습니다
블로킹은 요청한 작업을 마칠 때까지 계속 대기하며 return 값을 받아야 끝납니다.
Thread 관점으로 본다면, 요청한 작업을 마칠 때까지 계속 대기하며 return 값을 받을 때까지 한 Thread를 계속 사용/대기합니다.
논블로킹은 요청한 작업을 즉시 마칠 수 없다면 즉시 return합니다.
Thread 관점으로 본다면, 하나의 Thread가 여러 개의 IO를 처리 가능합니다.

 

동시성(Concurrency)은 Task들이 빠르게 전환하면서 실행되어 동시에 실행되는 것처럼 보이는 것입니다
즉, 동시성은 독립적인 작업을 작은 단위의 연산으로 나누어 시간 분할 형태로 연산하고 논리적으로 동시에 실행하는 것처럼 보이게하여 유휴 시간(Idle Time)을 최소화 하는 구조나 개념을 의미합니다.


여기서 유휴 시간은 컴퓨터가 작동 가능한데도 작업을 하지 않는 시간입니다.
같은 시간에 같은 자원에 접근하는 상황이 생길 수 있는데 해당 자원에 write 권한으로 접근하는 경우 '데이터의 무결성 유지'를 꼭 염두해둬야 합니다.

 

 

병렬성(Parallelism)은 물리적으로 동일한 시간에 작업을 동시에 수행하는 것 입니다.
동시성과는 다르게 병렬성은 여러 작업들을 코어, 프로세스, 컴퓨터등으로 동시에 수행할 수 있으며 꼭 멀티 코어 한 개 이상의 스레드가 동시에 수행하는 것에만 한정하는 것은 아닙니다.

 

 

동시성과 병렬성을 비교해보자면 다음과 같습니다
병렬성은 동시성에 포함되는 개념입니다.

 

또한 멀티 코어와 멀티 스레드에서 병렬성으로 작업하는 게 항상 성능상 이점이 있는 것은 아닙니다. I/O 요청 후 기다리는 작업이 많은 경우를 처리할 때 때로는 단일코어에서 동시성으로 처리 할 때 효율적일 수 있습니다.

 

동시성 여러 작업을 동시에 수행하므로 경합 상태(Race Condition), 데드락(Deadlock), 기아(Starvation)과 같은 문제가 발생할 수 있으니 주의하여 개발해야합니다

 

 

다음으로 싱글스레드와 멀티스레드에 대해 설명하겠습니다.
파이썬과 자바스크립트는 싱글스레드를 지향하는데요 이말이 뭔 뜻인지 아시나요??

운영체제 시간에 배우셨겠지만 까먹었을 수도 있으니

잠깐 빠르게 설명하면 싱글스레드는 프로세스가 단일 스레드로 동작하는 방식이고,


그림 프로세스2,3처럼
멀티스레드는 하나의 프로세스 내에서 둘 이상의 스레드가 동시에 작업을 수행하는 경우 입니다.

그림을 예로들면 프로세스 1,4입니다.

 

 

node.js는 싱글 스레드를 가지고 있습니다.

그러나 자바스크립트를 실행하는 스레드는 단 하나이므로 Node를 싱글스레드라고 합니다.

그리고 그 싱글스레드가 바로 이벤트 루프입니다.

 

그러면 여기서 'js,nodejs에서는 멀티스레드를 지원안하는가?'에 대해 의문이 들겠죠.
결론은 지원합니다. nodejs에서는 'worker_threads'모듈을 사용하면 멀티 스레딩이 가능하지만 nodejs의 핵심요소가 이벤트 루프 즉 싱글스레드 와 메인스레드이기 때문에 개념적으로 싱글스레드를 지향한다고 합니다.

 

 

파이썬도 멀티스레드를 지원안하는가에 대해 의문이 들 수 있는데 파이썬도 지원합니다 .

threading모듈을 쓰면 됩니다. 하지만 nodejs와 달리 파이썬은 글로벌 인터프리터 락이 존재하는데요.

파이썬에만 존재하는 독특한 개념으로 파이썬에서 멀티스레딩을 할 때

다수의 스레드가 동시에 파이썬 바이트 코드를 실행하지 못하게 막는 일종의 잠금입니다.

뭔소리인지 모르실까봐 풀어서 설명하면

GIL은 인터프리터에 락을 걸어 모든 시간에 하나의 선택된 스레드의 명령만 실행할 수 있다는 것입니다.

 

 

그림으로 보면 파이썬으로 작성된 프로세스는 한 시점에 하나의 스레드에만 모든 자원을 할당하고 다른 스레드는 접근할 수 없게 막아버리는데 이 역할을 GIL이 합니다.

 

그래서 멀티 스레딩을 하더라도 파이썬에선 우리가 생각하는 것처럼 여러 스레드가 동시에 작업을 하지 않습니다. 그래서 CPU-bound작업을 할때는 오히려 성능이 감소한다고 합니다.

 

근데 우리가 대부분 작성하는 코드는 CPU-bound 작업을 잘안해서 괜찮습니다 :)

해결방법은 thread가 아니라 프로세스를 추가로 생성하여 멀티프로세싱을 고려해야한다고 합니다. 원래 1개의 부엌 10명의 요리사 10개의 요리였는데 10개의 부엌으로 늘리라는 것이 해결방법입니다.

 

 

근데 왜 멀티스레드 잘만 쓰면 성능이 향상되는데 파이썬과 자바스크립트는 싱글스레드를 지향할까요?

파이썬의 경우에는 처음 고안이 되었을 때 멀티 스레드라는 개념이 없었습니다.

그래서 특별히 고려하지 않았다고 합니다.

 

자바스크립트 경우에도 당시 웹은 지금처럼 복잡한 구조가 아니여서 쉽고 빠르게 배울수 있으면서 브라우저에 잘 돌아가는 스크립트 언어를 고안하다보니 싱글스레드를 선택하게 되었다고 합니다.

 

하지만 현재에는 멀티 코어 CPU가 일반적으로 사용되고 잇기 때문에 멀티 스레드를 활용하는 것이 성능 개선에 매우 중요합니다. 이러한 이유에서 파이썬과 자바 스크립트에서 멀티스레드를 지원하기 위한 모듈이 등장한 것입니다.

 

 

그래서 파이썬의 GIL은 5년내에 삭제될 수도 있다고 합니다.

하지만 리스크가 너무 큰 것으로 판단 될 때 그결정을 취소할 수 있다고도 합니다 ~

 

 

다음으로 파이썬으로 비동기 프로그래밍 실습에 대해 설명하겠습니다. 목차는 파이썬으로 비동기 프로그래밍 실습, FastAPI로 비동기 프로그래밍 실습으로 이루어져있습니다.

 

 

여러분 코루틴 들어봤나요? 대부분 들어봤을텐데요 파이썬에서 코루틴 함수는 비동기함수랑 같은 말입니다.
다양한 진입점과 다양한 탈출점이 있는 루틴입니다.

 

Node.js에서처럼 파이썬도 async, await를 키워드를 사용해서 비동기 함수를 만듭니다.

왼쪽의 코드는 짜장면을 배달할 때 짜장면 배달원은 다먹을때까지 기다렸다가 그릇을 수거하지 않죠?

다음 배달하러 가잖아요 이것을 비동기로 짠 코드입니다.

 

 

코루틴을 사용하면 네이버,인스타그램, 구글을 크롤링했을때 사용하지 않았을 때보다 훨씬 시간이 줄어듭니다.

제가 직접 돌려보니 14초에서 2.07초로 줄어들은걸 볼 수 있었습니다.

 

 

다음으로 FastAPI를 이용해서 비동기프로그래밍 실습을 해볼건데요 여기서 FastAPI를 아시는 분은 별로 없으실거같아서 잠깐 소개를 해보자면 파이썬 웹 프레임 워크입니다.

대표적으로 여러분들도 알만한 파이썬 웹프레임워크는 장고가 있는데 FastAPI도 있습니다.

FastAPI는 nodejs처럼 비동기 동작으로 빠른 성능을 보장한다고 합니다.

 

 

get메소드 API를 만드는 코드인데요 .
async는 함수를 비동기 함수로 만들기 위해 붙여주고,

await는 비동기 함수에서 다른 비동기 함수를 호출할 때 붙여주면 됩니다.

 

 

db연결할때도 async with구문을 사용하여 비동기 컨텍스트 매니저를 사용할 수 있다고 합니다.

 

 

하지만 제가 fastapi공부하면서 느낀게 있는데 어차피 로직이 비동기가 아니라면

async await다 붙여도 동기적으로 짠코드랑 똑같습니다.

그러니 비동기 로직을 사용하지 않은 함수에 async/await를 남발하지 않는것이 좋은것 같습니다.

그래서 결론은 FastAPI의 동기 REST API와 비동기 REST API간의

선택은 애플리케이션의 특정 요구사항에 따라 다릅니다.


동기 API는 추론하기가 더 간단하지만 비동기 API는 동시작업을 처리할 때 좋은 선택이 됩니다.

효율적이고 성능이 뛰어난 Rest API를 설계하려면 각 접근 방식을 언제 사용해야 하는지

이해하는 것이 중요하다고 합니다.

 

 

예를들어 RESTAPI는 전자상거래 체크아웃, 사용자인증, 보고 및 분석 이런건 비동기 로직이 필요없습니다. 하지만 실시간 채팅, 소셜미디어 피드 이런건 비동기 REST API로 설계하는게 좋은 선택이죠.

 

이런식으로 API를 만들 때 비동기 로직을 사용할 수 있다면 사용해서 성능을 개선해 보는것도 좋을 거 같습니다. 

 

감사합니다.