<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sb-jo.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Sat, 28 Jan 2023 11:44:44 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sb-jo.log</title>
            <url>https://velog.velcdn.com/images/sb-jo/profile/79a4a8d7-ccb0-4cb6-9591-7d68e4e93bd3/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sb-jo.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sb-jo" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Python asyncio 시리즈(2) - async, await, coroutine, task, future]]></title>
            <link>https://velog.io/@sb-jo/asyncio-%EC%8B%9C%EB%A6%AC%EC%A6%882-async-await-coroutine-task-future</link>
            <guid>https://velog.io/@sb-jo/asyncio-%EC%8B%9C%EB%A6%AC%EC%A6%882-async-await-coroutine-task-future</guid>
            <pubDate>Sat, 28 Jan 2023 11:44:44 GMT</pubDate>
            <description><![CDATA[<h3 id="asyncio">Asyncio?</h3>
<p>asyncio를 본격적으로 다루기에 앞서, 1편에서 동기-비동기, 동시성-병렬성, 블록-논블록에 대해서 다루었다. 이번 편에서는 asyncio에 대한 소개와 asyncio 특징, 장단점 그리고 간단한 사용 방법들을 다루어보겠다.</p>
<p>asyncio란 이벤트 루프에 의해 스케줄링되고, 실행되는 코루틴을 이용해 동시성을 구현하는 라이브러리이다. asyncio는 이벤트 루프를 이용해 일련의 태스크를 실행하는 방식으로 작동한다.</p>
<p>asyncio의 특징은 다음과 같다.</p>
<ul>
<li>asyncio는 async, await 키워드를 통해 각 태스크에서 이벤트 루프로 제어권을 언제 넘겨줄 지, 특정 시점을 지정할 수 있다</li>
<li>선점형 멀티태스킹 방식이 아닌 협력적 멀티태스킹(cooperative multitasking)을 사용해 동시성을 달성한다.</li>
</ul>
<p>운영체제가 선점형 멀티태스킹을 이용해 여러 스레드 간 실행을 관리하는 것과 달리, asyncio는 협력적 멀티태스킹 방법을 이용해 여러 태스크 간 실행을 조율한다. 운영체제가 실행할 스레드를 변경하는 시점은 스케줄링 알고리즘에 따라 자동으로 작동하지만, 이것이 프로그램에 있어 최적의 스위칭 시기는 아닐 수 있다. 하지만, 협력적 멀티태스킹 방법은 프로그래머가 프로그램의 중지 시점을 명확하게 지정할 수 있다.</p>
<p>asyncio는 IO 작업이 발생했을 때, 운영체제의 이벤트 통지 시스템(event notification system)을 이용해 동시성을 구현하는데, 이는 3편 이벤트 루프 시리즈에서 다루어보도록 하겠다.</p>
<h3 id="코루틴">코루틴</h3>
<p>코루틴은 필요에 따라 일시 정지할 수 있는 함수라 볼 수 있다. 완료하는 데 오랜 시간이 걸리는 작업이 발생하면 일시 중지하고, 코드의 다른 부분을 실행할 수 있는 기능을 갖추고 있다.</p>
<p>이렇게 완료에 오랜 시간이 걸리는 작업을 기다리는 동안 다른 코드를 실행하여 애플리케이션의 동시성을 제공한다.</p>
<h3 id="async">async</h3>
<p>파이썬에서는 async 키워드를 이용해 코루틴을 만들 수 있다. 다음 예시를 살펴보자</p>
<pre><code class="language-python">async def my_coroutine() -&gt; None:
    print(&quot;My Corountine!&quot;)

&gt;&gt;&gt; my_coro = my_coroutine()
&gt;&gt;&gt; print(f&quot;Coroutine Type is {type(my_coro)})
Coroutine Type is &lt;class &#39;coroutine&#39;&gt;</code></pre>
<p>일반적인 함수는 호출 즉시 실행되지만, async 키워드를 이용해 생성한 코루틴은 코드가 실행되지 않고, 코루틴 객체를 반환한다.</p>
<p><strong>“코루틴을 호출할 때 코루틴이 실행되지 않는다”</strong>를 기억하자. 코루틴을 실행하는 다양한 방법을 asyncio에서 제공하는데, 우선 가장 간단한 <code>asyncio.run</code> 부터 살펴보자.</p>
<pre><code class="language-python">import asyncio

async def my_coro() -&gt; str:
    return &quot;Hello World&quot;

&gt;&gt;&gt; result = asyncio.run(my_coro())
&gt;&gt;&gt; print(result)
Hello World</code></pre>
<p><code>asyncio.run</code> 은 여러 작업을 수행한다.</p>
<ol>
<li>새로운 이벤트를 생성한다.</li>
<li>성공적으로 이벤트가 생성되면, 전달된 코루틴이 완료될 때까지 실행하고 결과를 반환한다.</li>
</ol>
<p>asyncio.run에서 중요한 점은 해당 함수가 애플리케이션의 주요 진입점이 되도록 설계되었다는 것이다. 하나의 코루틴만 실행하고 해당 코루틴이 애플리케이션의 다른 코루틴을 실행하게끔 프로그램을 디자인할 수 있다. </p>
<p><code>asyncio.run</code> 이 하는 일을 보다 구체적으로 알기 위해 다음 예시를 살펴보자.</p>
<pre><code class="language-python">async def my_coro() -&gt; str:
    return &quot;Hello World&quot;

my_coro = my_coroutine()

try:
    my_coro.send(None)
except StopIteration as e:
    print(&#39;Coroutine Stopped. Return Value is :&#39;, e.value)

&gt;&gt;&gt; Coroutine Stopped. Return Value is : 123</code></pre>
<ol>
<li>코루틴 객체 생성</li>
<li>코루틴에 None을 전달(send)해 초기화</li>
</ol>
<p>asyncio에서 제공하는 API들은 대부분 위와 동일한 방식으로 코루틴을 초기화한다. 즉, <code>loop.create_task(coro)</code>, <code>asyncio.run(coro)</code> 혹은 <code>await coro</code> 를 실행하면 이벤트 루프가 알아서 내부적으로 send(None)을 실행한다.</p>
<h3 id="await">await</h3>
<p>위의 예시들은 실행 결과를 바로 반환한다는 점에서 일반 파이썬 함수와 차이점이 없다. asyncio의 장점은 await 키워드를 이용해 실행을 일시 중지하여 이벤트 루프가 다른 작업을 실행할 수 있도록 하는 것이다.</p>
<p>await 키워드 뒤에는 awaitable 객체가 온다. await 키워드를 사용하면 코루틴 객체를 생성하는 코루틴을 호출하는 것이 아니라, await 키워드 뒤에 오는 코루틴을 실행한다.</p>
<p>await 키워드는 뒤에 오는 awaitable 객체가 완료되고 결과를 반환할 때까지 await 키워드를 포함하고 있는 상위 코루틴이 일시중지한다. 기다리던 await 키워드 뒤의 하위 코루틴이 완료되면 상위 코루틴이 재개되어 반환된 결과에 접근할 수 있으며 변수에 할당 가능해진다. </p>
<p>말이 조금 복잡한데 아래 코드를 살펴보자.</p>
<pre><code class="language-python">import asyncio

async def my_coro() -&gt; str:
    return &quot;Hello World&quot;

async def main() -&gt; None:
    result = await my_coro()
    print(result)

asyncio.run(main())</code></pre>
<ul>
<li>main() 코루틴이 실행되고 await 키워드에 도달한다</li>
<li>await 키워드 뒤에 오는 코루틴인 my_coro 코루틴이 실행되고, 상위 코루틴인 main() 코루틴이 일시 중지된다.</li>
<li>my_coro()가 실행 완료되면, 반환값인 “Hello World”에 접근할 수 있게 되고, 다시 main() 코루틴이 실행 재개된다.</li>
</ul>
<p>여기까지만 봐도 아직까진  파이썬 함수와 다른 점을 살펴볼 수 없을 것이다. asyncio의 이점을 볼 수 있는 장기 실행 작업을 이제 살펴보자.</p>
<pre><code class="language-python">import asyncio

async def my_coroutine() -&gt; &quot;str&quot;:
    print(&quot;Coroutine Entry Point&quot;)
    await asnycio.sleep(1)
    print(&quot;Coroutine Second Entry Point&quot;)

async def main() -&gt; None:
    await my_coroutine()
    await my_coroutine()

asyncio.run(main())</code></pre>
<p>실행이 오래 걸리는 작업을 흉내내기 위해 asyncio.sleep() 함수를 사용한다. asyncio.sleep()은 코루틴이므로 await 키워드 뒤에 사용한다.</p>
<p>위의 예시를 실행하면 다음과 같은 결과가 나타난다.</p>
<pre><code>Coroutine Entry Point
Coroutine Second Entry Point
Coroutine Entry Point
Coroutine Second Entry Point</code></pre><p>왜 이런 결과가 나타날까. 우리는 실행이 오래 걸리는 작업(여기에서는 asyncio.sleep(1)) 동안 다른 코드가 실행되기를 원했지만, 위의 예시에서는 순차적으로 코드가 실행되었다.</p>
<p>그 이유는 await 키워드에 있다. await은 현재 코루틴을 일시 중지하고, await 키워드 뒤의 코루틴이 작업을 완료할 때까지 다른 코드를 실행하지 않는다. 이러한 순차 실행이 아니라, 비동기적으로 코드를 실행하고 싶다면 <code>Task</code>를 사용해야 한다.</p>
<h2 id="task">Task</h2>
<p>Task는 이벤트 루프에 코루틴을 예약하는 코루틴의 래퍼(wrapper)이다. 코루틴 스케줄링 및 코루틴 실행은 논블로킹 방식으로 일어나며, Task를 생성하면 Task가 실행되는 동안 다른 코드를 실행할 수 있다.</p>
<pre><code class="language-python">import asyncio

async def my_coroutine() -&gt; &quot;str&quot;:
    print(&quot;Coroutine Entry Point&quot;)
    await asnycio.sleep(1)
    print(&quot;Coroutine Second Entry Point&quot;)

async def main():
    task1 = asyncio.create_task(my_coroutine())
    task2 = asyncio.create_task(my_coroutine())
    print(type(task1))
    await task1
    await task2

asyncio.run(main())</code></pre>
<p>위의 예시를 실행하면 다음과 같은 결과가 나온다.</p>
<pre><code>&lt;class &#39;_asyncio.Task&#39;&gt;
Coroutine Entry Point
Coroutine Entry Point
Coroutine Second Entry Point
Coroutine Second Entry Point</code></pre><p>이제야 순차 방식이 아닌, 비동기 방식으로 코드가 실행됨을 확인할 수 있다.</p>
<p>위의 예시에서 먼저 <code>asyncio.create_task(coro)</code> 활용해 Task를 생성한다. Task는 생성됨과 동시에 코루틴을 이벤트 루프에 예약하고 실행한다.</p>
<p>이렇게 2개의 태스크를 생성한 후에 해당 태스크를 await하게 된다. 여기서 주목해야할 점은 await 키워드를 “특정 시점”에서 “반드시 사용”해야 한다는 점이다.</p>
<p>“특정 시점”과 “반드시 사용”이 중요하다. 멀티 스레딩과 비교했을 때 asyncio의 장점으로 프로그래머가 애플리케이션의 일시 중지 시점과 실행 재개 시점을 지정할 수 있다는 점을 들고는 한다. 위 예시에서도 여러 태스크를 한번에 생성한 후, 뒤늦게 await 키워드를 통해 해당 태스크의 실행 완료를 기다렸다.</p>
<p>만약, await 키워드를 이용해 태스크의 실행 완료를 기다리지 않는다면, <code>asyncio.run()</code> 이 이벤트 루프를 종료할 때 해당 태스크의 실행 완료를 기다리지 않고 정리하는 문제가 발생한다.</p>
<p>지금은 2개의 태스크를 생성하는 예시를 들었지만, asyncio는 무수히 많은 태스크의 실행 또한 처리 가능하다. 태스크는 즉시 생성되고 가능한 빨리 실행되도록 이벤트 루프에 예약되기 때문에 많은 수의 태스크가 동시에 실행되도록 작성할 수 있다.</p>
<pre><code class="language-python">async def main():
    tasks = []
    for _ in range(10):
        task = asyncio.create_task(hello_world_message())
        tasks.append(task)

    for task in tasks:
        await task</code></pre>
<p>위의 예시에서는 총 10개의 태스크를 생성했다. 각각의 태스크는 1초의 대기 시간을 갖게 되는데, 위 코드의 총 실행 시간은 10초가 아닌 1초가 조금 넘는다. </p>
<h3 id="awaitable">awaitable</h3>
<p>앞서 await 키워드 뒤에는 awaitable 객체가 온다 했는데, 그렇다면 awaitable 객체란 무엇일까?</p>
<p>파이썬에서 awaitable 객체는 크게 코루틴, 태스크, 퓨처(Future)이다. asyncio API와 asyncio로 동작하는 여러 라이브러리들을 이해하기 위해서는 이 3가지에 대해 모두 알아야 한다. 먼저 Future에 대해 살펴보자.</p>
<h3 id="future">Future</h3>
<p>Future는 미래의 어느 시점에 값을 얻을 것으로 예상하지만, 아직 얻지 못한 객체를 나타낸다. Future는 결과를 설정(set_result)해줌으로써 완성된다. 즉, Future의 결과가 설정되어야 해당 객체가 완료된 것으로 간주하고 Future에서 결과를 추출할 수 있다.</p>
<pre><code class="language-python">&gt;&gt;&gt; from asyncio import Future
&gt;&gt;&gt; fut = Future()
&gt;&gt;&gt; fut.done()
False
&gt;&gt;&gt; fut.set_result(&quot;VALUE&quot;)
&gt;&gt;&gt; fut.done()
True
&gt;&gt;&gt; fut.result()
VALUE</code></pre>
<p>Future가 제공하는 메소드는 다음과 같다.</p>
<ul>
<li>done() : 완료 여부 확인</li>
<li>add_done_callback() : 완료 시 실행할 콜백 함수 등록</li>
<li>result() : Future 결과 확인</li>
</ul>
<h3 id="future와-task-관계">Future와 Task 관계</h3>
<p>태스크는 Future를 상속한 객체이다. 태스크는 코루틴과 Future의 조합으로 볼 수 있으며, 이벤트 루프 내에서 코루틴을 실행하기 위해 사용된다. 우리가 태스크를 생성하면 먼저 Future를 만들고 코루틴을 실행한다. 그런 다음 코루틴이 완료되면 Future의 결과 또는 예외를 설정한다. </p>
<p>코루틴, Future, Task를 정리하면 다음과 같다.</p>
<ul>
<li>코루틴 : async def 키워드로 정의한 결과가 코루틴 객체이며, 아직 await하지 않은 상태이다.</li>
<li>asyncio.Future : 최종 결과를 나타내는 클래스이며, 결과를 수동으로 설정 가능하다.</li>
<li>asyncio.Task : 편리하고 일관된 인터페이스 갖도록 코루틴을 래핑하기 위한 asyncio.Future의 하위 구현이다.</li>
</ul>
<h2 id="task-취소">Task 취소</h2>
<p>IO 위주 작업의 경우 asyncio의 장점을 최대한 살릴 수 있다. 대표적인 IO 위주 작업인 네트워크 연결을 예로 들어보자.</p>
<p>네트워크 연결은 불안정할 수 있으며, 예상치 못한 상황으로 인해 연결이 끊어지거나 요청에 대한 응답을 반환받지 못할 수 있다. asyncio에서 태스크를 생성해 네트워크 작업을 처리하면, 네트워크 요청이 무한정 지연되는 상황이 발생할 수 있다. 이렇게 특정 시간 이상 걸리는 작업에 대해 특별한 조치가 필요한데, asyncio는 다양한 방법으로 이를 지원한다.</p>
<h3 id="taskcancel">Task.cancel</h3>
<p>먼저 태스크 취소에 대해 알아보자. 태스크는 <code>cancel</code> 메소드를 통해 작업 중지가 가능하다. 태스크가 취소되면 <code>CancelledError</code>가 발생한다.</p>
<pre><code class="language-python">import asyncio

async def long_delay_coro() -&gt; None:
    await asyncio.sleep(5)
    print(&quot;Long Delay Coroutine Finished&quot;)

async def main():
    task = asyncio.create_task(long_delay_coro())
    task.cancel()

    try:
        await task
    except asyncio.CancelledError:
        print(&quot;Long Delay Coroutine Cancelled&quot;)

asyncio.run(main())</code></pre>
<p>위 예시에서 주목할 점은 태스크가 취소되었을 때 바로 CancelledError가 발생하지 않는다는 점이다. asyncio에서의 예외는 다음에 더 자세히 다뤄보겠다. 일단은, CancelledError가 cancel 메소드가 일어난 시점에 바로 발생하는 것이 아니라는 점을 알아두자.</p>
<h3 id="asnyciowait_for">asnycio.wait_for</h3>
<p>위의 예시는 5초가 걸리는 코루틴을 바로 실행 취소했다. 특정 태스크를 주어진 시간 동안 기다리고, 그 시간 안에 완료되지 않았다면 예외를 발생시키는 기능이 존재한다.</p>
<pre><code class="language-python">import asyncio

async def long_delay_coro() -&gt; None:
    await asyncio.sleep(5)
    print(&quot;Long Delay Coroutine Finished&quot;)

async def main():
    task = asyncio.create_task(long_delay_coro())
    try:
        await asyncio.wait_for(task, timeout=2)
    except asyncio.exceptions.TimeoutError:
        print(&quot;2초 경과&quot;)</code></pre>
<p><code>wait_for</code> 함수는 주어진 timeout 시간 동안 결과를 기다리고, TimeoutError를 발생시킨다. </p>
<h3 id="asyncioshield">asyncio.shield</h3>
<p>태스크가 특정 시간보다 오래 걸리는 경우 추가 처리를 진행하되 태스크를 취소하지는 않고 싶은 경우 asyncio.shield로 태스크를 래핑하여 태스크가 취소되는 것을 방어한다.</p>
<pre><code class="language-python">asyncio.wait_for(asyncio.shield(task), timeout=2)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python asyncio 시리즈(1) - 비동기]]></title>
            <link>https://velog.io/@sb-jo/asyncio-%EC%8B%9C%EB%A6%AC%EC%A6%881-%EB%B9%84%EB%8F%99%EA%B8%B0</link>
            <guid>https://velog.io/@sb-jo/asyncio-%EC%8B%9C%EB%A6%AC%EC%A6%881-%EB%B9%84%EB%8F%99%EA%B8%B0</guid>
            <pubDate>Sat, 28 Jan 2023 11:41:58 GMT</pubDate>
            <description><![CDATA[<p>동기와 비동기, 동시성과 병렬성, 블로킹과 논블로킹. 검색하다보면 같이 자주 언급되는 단어들이며 비슷한 발음(동시-동기과와 비슷한 의미 덕분에 혼용되곤 한다. asyncio를 시작하기에 앞서 이러한 내용들을 우선 정리해보고자 한다.</p>
<h3 id="동시성-병렬성">동시성-병렬성</h3>
<p>동시성과 병렬성의 차이를 실생활에서 설명하는 예시로 레스토랑의 셰프를 들 수 있다. 레스토랑에서 오래 근무한 능숙한 셰프라면 파스타의 면을 삶으면서, “동시”에 파스타 소스를 끓일 수 있을 것이다. 이렇게 여러 일을 동시에 하는 경우를 “동시성”이라 표현한다. 컴퓨터의 경우라면 운영체제를 예로 들 수 있다. 우리는 유튜브로 노래를 들으면서 문서 작업을 할 수 있다. 이는 운영체제가 CPU의 단일 프로세스 코어에서 여러 프로그램을 아주 빠르게 번갈아가며 실행하기에 가능한 것이다. 즉, 운영체제는 프로그램이 마치 동시에 실행되는 것 같은 효과를 사용자에게 가져다준다.</p>
<p>병렬성이란 2명의 셰프가 각각 다른 요리를 하는 것을 뜻한다. 즉 1명의 셰프가 아니라 2명 혹은 그 이상의 셰프가 각각 자신만의 요리를 할 때 병렬성이라 표현한다. 그렇다고 해서 동시성의 개념이 물리적으로 CPU 1개 코어에서만 작동하는 개념은 아니다. 동시성은 제한된 자원에서 여러 작업을 한번에 실행시키는 논리적 개념이라 보는 것이 더 타당하다.</p>
<p>정리하면, “동시성”은 프로그램, 알고리즘 또는 문제의 속성 중 하나이며 병렬성은 이렇게 동시에 발생하는 문제를 해결하는 접근 방식의 하나이다.</p>
<p>다시 “동시성”에 대해 좀 더 생각해보자. 파스타 면을 삶으면서, 파스타 소스를 끓일 수 있는 이유는 무엇일까? 파스타 소스를 끓일 때는 아직 파스타 면이 필요하지 않기 때문이다. 또한, 파스타 면을 삶는 냄비와 파스타 소스를 끓이는 팬이 다르기 때문이다 즉, 동시성은 각각 따로 발생하는 2개의 이벤트가 서로 독립적이어서 영향을 끼치지 않을 때 가능하다.</p>
<p>왜 “독립적”인 특성이 중요할까. 각각의 이벤트(혹은 프로그램/알고리즘/해결하고자 하는 문제)가 서로 완전히 독립적이고, 각각의 컴포넌트로 분리할 수 있다면 이들의 처리 순서가 최종 결과에 영향을 주지 않게 된다. 즉, A→B→C와 같은 순차적 흐름이 필요없게 되므로, 이러한 문제를 병렬성을 적용할 수 있게 된다. (물론, 병렬 프로세싱이 필수적인 것은 아니다)</p>
<p>동시성을 달성하는 다양한 방법들이 존재한다. 그 기법들은 다음과 같다.</p>
<ol>
<li>멀티 스레딩 - python threading module</li>
<li>멀티 프로세싱 - python multiprocessing module</li>
<li>비동기 처리(Asynchronous Processing) - generator, coroutine, concurrent.futures, asyncio</li>
</ol>
<h3 id="동기-vs-비동기">동기 vs 비동기</h3>
<p>이번 시리즈에서 지속적으로 다룰 asyncio는 asynchronous IO(비동기 IO)의 약자로 파이썬에서 비동기 프로그래밍을 사용해 코드를 실행할 수 있도록 도와주는 라이브러리이다.
먼저, 동기(synchronous)와 비동기(asynchronous)에 대해서 알아보자.
<img src="https://velog.velcdn.com/images/sb-jo/post/e0cfc4f7-6851-47ea-a3a3-fd282cf99566/image.png" alt="출처: https://medium.com/from-the-scratch/wtf-is-synchronous-and-asynchronous-1a75afd039df"></p>
<p>동기 방식이라 함은, 작업 A가 작업 B에게 작업 요청을 하고, 작업을 끝낼 때까지 관심을 가지고 기다리는 방식을 뜻한다.</p>
<p>비동기 방식은 작업 A가 작업 B에게 작업 요청을 하고, 작업을 끝낼 때까지 관심을 버리고 기다리지 않는다. 앞서 동시성을 구현하는 방법으로 비동기가 가능하다 말했는데, 그 이유가 여기에 있다. 작업 A가 작업 B에게 작업 요청을 하고, 작업 A는 작업 B를 기다리지 않고 자신의 일을 한다. 즉, 작업 A와 작업 B가 동시에 실행되어 동시성을 달성할 수 있다.</p>
<p>비동기 방식으로 코드를 작성하면, 응답에 대해서 처리를 하는 코드를 따로 작성해야 한다. 관심이 없더라도, 작업 B가 작업을 끝냈는지 결국은 확인해야 한다. 이때, 콜백(Callback) 방식을 많이 활용한다</p>
<p>비동기 실행 모델에서 작업(태스크)은 스케줄러에 의해 선택되고 실행된다. 스케줄러는 인터리브 방식(interleaved manner)으로 태스크 큐에서 태스크를 선택하고 실행한다.</p>
<p>비동기 프로그램을 구현할 때는 항상 다음과 같은 질문을 염두에 두어야 한다.</p>
<ul>
<li>비동기로 요구한 처리가 끝나지 않은 상태에서 다음 처리를 진행해도 문제가 없는가</li>
<li>비동기로 요구한 처리가 끝났는지 확인할 필요가 있는가</li>
</ul>
<p>예시를 들어보자. 작업 A가 작업 B에 고객이름과 데이터를 보내면, 작업 B는 해당 데이터를 고객에게 메일로 보낸다 가정하자. 작업 A는 작업 B가 메일을 성공적으로 보냈음을 반드시 알아야할까? 그럴 수도 있고, 아닐 수도 있다. 어떻게 설계하느냐에 따라 달라지는데, 만약 작업 B가 메일을 성공적으로 보냈는지 여부를 다른 콘솔 창 혹은 로그 파일에 기록하게끔 설계한다면, 작업 A는 굳이 메일 성공 여부를 확인할 필요가 없게 된다.</p>
<p>그렇다면, 블락과 논블락은 무엇일까?</p>
<h3 id="블락---논블락">블락 - 논블락</h3>
<p>블락 - 논블락은 기술적으로 함수 호출에서 제어권에 대한 이야기이다. </p>
<p>함수 A가 함수 B를 호출했을 때 프로세스 제어권이 함수 B로 넘어가는 경우 함수 B를 블로킹 함수라 한다.</p>
<p>반면, 함수 A에서 함수 B를 별도의 스레드로 생성하고, 특정 객체를 바로 리턴받는 경우 논블록이라 한다. 함수 A가 있는 스레드는 함수 B를 별도의 스레드로 만든 이후 자신의 일을 이어서 할 수 있기 때문에 블록킹되지 않는다. 별도의 스레드라고 표현했지만, 스레드가 아니더라도 논블록킹 함수 구현이 가능하다.</p>
<p>정리하면, 동기와 비동기는 작업 완료에 관심이 있느냐에 대한 작동 방식에 좀 더 초점이 있고, 블로킹과 논블로킹은 프로세스 제어권이 넘어가느냐에 대한 내용에 초점을 맞춘다.</p>
<h3 id="why-asyncio">why asyncio?</h3>
<p>asyncio가 필요한 이유에 대해서 살펴보자.</p>
<p>I/O 위주의 프로그램은 I/O 효율이 프로그램 속도에 중요한 영향을 미친다. 파일이나 네트워크 소켓에서 데이터를 읽을 때마다 프로그램은 잠시 실행을 멈추고 커널에 IO 연산 수행을 요청한다. 커널만이 하드웨어와 상호작용할 수 있고, 실제 읽기 연산의 주체가 커널이기 때문이다.</p>
<p>문제는 CPU와 IO 연산의 속도 차이에 있다. I/O 연산 대부분은 CPU보다 매우 느리게 작동한다. 그렇기에 프로그램이 네트워크 혹은 파일로부터 데이터가 도달하기를 기다리는 동안 CPU가 아무 일도 하지 않고 대기하는 일이 발생한다. 이렇게 I/O 도중 프로그램이 일시 정지된 상태를 I/O 대기(I/O wait)라 한다.</p>
<p>비동기 I/O는 커널이 I/O 연산을 수행하는 동안 다른 연산을 수행해 대기 시간을 줄이고, 자원을 최대로 활용하기 위해 필요하다.</p>
<p>그렇다면 비동기 I/O는 어떻게 동작할까? 비동기 I/O는 운영체제의 도움을 받아야 한다. 운영체제의 커널이 데이터 읽기 및 쓰기 요청을 하드웨어에 보내고, 하드웨어로부터 데이터가 준비 완료되면 신호를 보낸다. 프로그램은 데이터를 기다리는 대신, 다른 작업을 수행하다가 나중에 데이터가 준비되면 통지받는다.</p>
<p>이러한 메커니즘으로 이벤트 루프를 주로 사용한다. 이벤트 루프는 메시지 디스패처(message dispatcher)라고도 부르며, 프로그램의 이벤트나 메시지를 대기하다가 준비 완료되면 메시지를 디스패치(통지, 전송)한다.</p>
<p>프로그램이 I/O 대기에 들어가면 프로그램 실행을 멈추고 컨텍스트 스위칭이 발생해 커널이 I/O 관련 연산을 처리하기 시작한다. 컨텍스트 스위칭은 나중에 다시 프로그램을 재실행하기 위해 프로그램의 상태를 저장해야 한다. </p>
<p>이벤트 루프 또한 마찬가지이다. I/O 관련 작업을 비동기 방식으로 요청하고, 나중에 데이터가 준비되었을 때 통지받는다 생각하자. 통지받은 이후에는, 전달받은 데이터로 이후의 추가 작업을 진행해야 할 것이다. 그러기 위해서는 “어디”부터 다시 연산을 진행할 것인지와 전달받은 데이터를 “어떤” 함수(혹은 이벤트 핸들러)에 전달해 실행할 것인지를 관리해야 한다. 이벤트 루프는 이렇게 실행할 대상과 시점을 관리한다.</p>
<p>이벤트 루프를 사용하는 프로그래밍은 콜백과 퓨처(Future) 두 형태 중 하나로 구현이 가능하다. 콜백 방식은 파이썬 3 초반 버전 이전에 인기 있었다. 이후 등장한 asyncio 표준 라이브러리는 퓨처 메커니즘을 채택했다. 이후에 더 자세히 다루겠지만 퓨처는 미래에 얻을 결과에 대한 약속으로, 퓨처에 I/O 연산 결과 값이 채워지기를 기다리는 방식이다.</p>
]]></description>
        </item>
    </channel>
</rss>