Computer Science/컴퓨터네트워크(ComNet)

[컴네/CN] 비동기 프로그래밍

gxxgsta 2023. 12. 4. 01:27
반응형
SMALL

Blocking vs. Non-blocking

Blocking

프로세스가 시스템을 호출하고 나서 결과가 반환되기까지 다음 처리로 넘어가지 않는 상태.

즉, block이 된 상태를 말한다.

 

Non-blocking

시스템을 호출한 직후에 프로그램으로 제어가 돌아와

시스템 호출의 종료를 기다리지 않고 다음 처리로 넘어갈 수 있다.

I/O(입출력)가 길어지는 경우 블로킹이면 대기시간이 길어지기 떄문에 논블로킹 사용한다.

 

 

좌측 코드는 서버, 우측 코드는 클라이언트의 코드이다.

 

서버 코드에서는 소켓을 바인딩하고, while문을 통해 accept(연결)를 기다린 후

데이터를 수신한다.

 

클라이언트 코드에서는 ip 주소와 포트번호를 설정하여 연결을 생성하고

"HelloWorld\n"라는 문자열을 300 * 1024 * 1024만큼 반복한 후

해당 데이터를 socket.send()함수로 전송한다.

이때 클라이언트 코드에서 블로킹과 논블로킹의 모드를 설정할 수 있는데,

해당 소켓에 대해 1이면 blocking, 0이면 non-blocking 모드이다.

 

클라이언트 코드에서 전송하려는 데이터가 3GB를 넘는데,

대량의 데이터 전송시 send 함수가 오류가 날 수 있다.

이러한 오류는 운영체제, 시스템 환경변수 등에 따라 달라지며,

blocking나 non-blocking 모두 발생한다.

 

이러한 오류는 맥 os에서 더 빨리 발생하고,

윈도우 os에서는 데이터의 길이가 2GB를 초과해야 발생하였다.

 

블로킹 모드의 경우 제어권이 없으므로 완료까지의 대기 시간이 길다.

즉, 오류를 반환하기 까지의 시간이 길다.

하지만 네트워크가 좋거나, 버퍼의 사이즈가 작으면 오류 리턴이 빠르므로

실험 환경에 따라 변화가 존재한다.

 

위 경우 소켓에서 시스템 콜을 하였는데, 운영체제의 소켓 버퍼 사이즈의 디폴트 크기가 달라 큰 용량의 데이터가 한 번에 전송되면 오류가 발생한다.

 

이러한 경우 오류를 어떻게 해결할 수 있을까?

1. send함수를 sendAll로 수정한다.

2. 반복문을 통해 해결한다.

 

1번의 경우 함수 이름만 바꿔주면 되므로 생략하고

2번의 경우에 대해 코드로 살펴 보자.

 

 

위의 코드는 정상적으로 작동하는 코드이다.

socket.send 함수로 데이터의 전체를 전송하지 않는다는 가정 하에

지금까지 전송한 데이터를 기록하여 그 이후부터의 데이터를 다시 전송하는 과정을

반복하여 데이터를 끝까지 전송할 수 있다.

 

이때, 데이터 전송 시 오류가 나면 타임 아웃되어 해당 소켓이 idle한 상태

(아무것도 하지 않는 상태)일 때가지 기다렸다가 이후 select한다.

 

위의 코드에서는 3GB를 두 번에 나누어 전송한 상태로

소켓 커널 버퍼 사이즈로 인해 발생할 수 있는 오류를 보여준 예제이다.

 

우리가 알아야 할 것은 blocking 방법으로 큰 용량의 데이터를 전송 시 I/O가 길어지므로

I/O의 길어짐을 방지하기 위해 non-blocking 방식을 사용한다는 것이다.

 

비동기 프로그래밍(Asynchronous Programming)

동기(Synchronous)

특정 작업이 끝나면 다음 작업을 처리하는 순차 처리 방식으로

결과가 끝나길 기다렸다가 다음 작업을 처리한다.

 

비동기(Asynchronous)

pipelining 방식으로, 여러 작업이 실행되는 상황에서 작업을 순차적으로 처리하지 않고

각 작업이 끝나지 않은 상태(미종료)에서 여러 작업을 동시에 처리한다.

 

즉, 여러 작업을 처리하도록 예약한 뒤 작업이 끝나면 결과를 받는 방식이며

cpu 연산이 DB/api 연산보다 훨씬 빠르기 때문에

시간에 대한 버퍼링을  진행하기 위하여 비동기 방식이 도입되었다.

 

 

 

위 예제는 프로그램이 동기적으로 실행되는 모습을 보여주는 것이다.

결과를 보면 모든 처리가 순차적으로 진행됨을 확인할 수 있으며,

task를 시작하여 종료까지의 시간이 6.005...초가 소요됨을 확인할 수 있다.

 

 

위 예제는 비동기 프로그래밍을 시행한 예제이다.

비동기 프로그래밍을 하기 위해서는 몇 가지 명령어를 알아야 하는데,

아래에서 자세히 알아보고 지금은 대충 넘어가도록 하자.

 

결과값을 확인 했을 때 동기 프로그래밍과 다르게

각 task가 건너뛰면서(cross) 수행되고 있음을 확인할 수 있다.

즉, 여러 task가 동시에 시행되고 있음을 알 수 있다.

 

예제2

비동기 프로그래밍

몇 가지 신택스를 공부, 어싱ㅋ으ㅔ 대한 io 모듈이 존재, import, asny 쓰고 대기

createtask 생성, 루프 도는 작업 필요

메인에 이벤트 뤂 만듦 끝나는 걸 기다렸다가 리턴, 루프 종료

이런 작업이 동시에 진행

3개를 만들어서 실행함

아까처럼 3 2 1이 아니라 실행결과에서 1, 2, 3 루프에서 task 안에서의 순차가 아니라

task를 건너 뛰어서 사용자를 조회함

여러 테스크가 동시에 시행되는 결과

-> 비동기가 이렇구나...

 

동시 프로그래밍 패러다임

운영체제에서의 프로그래밍 뿐만 아니라 여러 개의 task를 동시에 처리할 때

전통적으로 스레드를 통해 처리하였다.

 

스레드는 프로세스보다 가볍다는 장점이 있지만

스레드 세이프 프로그래밍이 어렵다는 단점이 있다.

 

thread safe programming

여러 스레드가 어떤 함수, 변수, 객체에 접근하여도 프로그램 실행에 문제가 없는 것

thread-local storage, mutual exclusion(lock/semaphore), atomic operation, immutable object 등

 

core 개수보다 스레드가 많아지면 성능 향상이 거의 없거나 저조할 수 있다.

즉, 성능이 코어의 개수에 민감하다.

 

따라서 최근에는 하나의 스레드로 동시에 여러 task를 처리하는 비동기 프로그래밍을 진행한다.이러한 기법은 js, nginx 등에서 주로 사용된다.

 

 

위 예제는 web search 예제로 리스트에 있는 6개의 단어에 대해

구글에서의 검색 결과값을 출력하는 데에 소요되는 시간을 측정하는 코드이다.

urlopen부터 응답하는 시간을 측정한다.

 

루프를 돌면서 6번을 순서대로 시행하여 4.8초의 시간이 소요됨을 확인할 수 있다.

 

파이썬에서의 비동기 프로그래밍

anyncio 모듈을 이용한다.

 

- async 함수를 사용하기 위해 코루틴(coroutine, 함께 동작하여 규칙이 있는 일의 순서) 정의

   async def로 만든 코루틴(python 3.5 <= )

 

- 여러 개의 작업을 수행하기 위해 이벤트 루프 생성

   loop = asyncio.new_event_loop()

   asyncio.set_event_loop(loop)

 

- 코루틴 객체를 이용하여 코루틴 실행 결과 대기

   loop.run_until_complete(hello())

 

- 루프 닫기
   loop.close()

 

코루틴 객체, 퓨처 객체, 태스크 객체 지정하여 끝날 때까지 기다린 뒤 결과 반환하게 하는 키워드: await

 

비동기로 웹페이지 가져오기

- 동기

여러 개의 웹페이지를 순차적으로 가져 온다.

웹 페이지 1개 완료 후 다음 웹 페이지 실행한다.

 

-비동기

여러 개의 웹페이지를 동시에 가져온다.
여러 개의 태스크 객체 생성 및 동시 실행
• asyncio.ensure_future(): 태스크 객체 생성 후 리스트로 만들기


태스크 리스트 결과 기다리기

• asyncio.gather()


블로킹 I/O 함수(urlopen, response.read) 병행 실행: 쓰레드 병렬 실행

• run_in_executor()

 

 

위의 코드는 앞서 이야기 한 asyncio 모듈을 통해 비동기로 웹 서치를 시행한 결과라고 할 수 있다.

6개의 키워드를 fetch함수를 통해 요청하고,

await loop.run_in_executor() 함수를 통해 task를 수행한다.

이후 응답 페이지를 리턴한다.

 

실행 시간을 보면 0.918초로 동기 프로그래밍으로 시행한 것보다

훨씬 빠른 것을 확인할 수 있다.

 

I/O가 길 때 하드디스크, DB, I/O 처리 시 순차적 처리보다 비동기화하는 게 더 속도 향상에 도움이 된다.

 

아래 그림을 통해 순차적으로 동기화된 작업과, 비동기로 여러 개의 작업을 처리 하는 모습을 볼 수 있다.

반응형
LIST