Python에서 비동기 시작하기

비동기 프로그래밍 또는 간단히 비동기 는 프로그램이 여러 작업을 기다리거나 중단하지 않고도 여러 작업을 처리 할 수 ​​있도록하는 많은 현대 언어의 기능입니다. 작업이 완료 될 때까지 대부분의 프로그램 시간이 소요되는 네트워크 또는 파일 I / O와 같은 작업을 효율적으로 처리하는 현명한 방법입니다.

100 개의 네트워크 연결을 여는 웹 스크래핑 애플리케이션을 고려하십시오. 하나의 연결을 열고 결과를 기다린 다음 다음 연결을 열고 결과를 기다릴 수 있습니다. 프로그램이 실행되는 대부분의 시간은 실제 작업을 수행하지 않고 네트워크 응답을 기다리는 데 소비됩니다.

Async는보다 효율적인 방법을 제공합니다. 100 개의 연결을 한 번에 모두 연 다음 결과를 반환 할 때 각 활성 연결간에 전환합니다. 하나의 연결이 결과를 반환하지 않는 경우 모든 연결이 데이터를 반환 할 때까지 다음 연결로 전환하는 식입니다.

비동기 구문은 이제 Python의 표준 기능이지만 한 번에 한 가지 일을하는 데 익숙한 오랜 Pythonistas는 머리를 감싸는 데 어려움을 겪을 수 있습니다. 이 기사에서는 비동기 프로그래밍이 Python에서 작동하는 방식과이를 사용하는 방법을 살펴 봅니다.

Python에서 비동기를 사용하려면 Python 3.7 또는 Python 3.8 (이 문서 작성 시점의 최신 버전)을 사용하는 것이 가장 좋습니다. 해당 언어 버전에 정의 된대로 Python의 비동기 구문과 도우미 함수를 사용합니다.

비동기 프로그래밍을 사용하는 경우

일반적으로 비동기를 사용하는 가장 좋은시기는 다음과 같은 특성을 가진 작업을 수행하려고 할 때입니다.

  • 작업을 완료하는 데 오랜 시간이 걸립니다.
  • 지연에는 계산이 아닌 I / O (디스크 또는 네트워크) 작업을 기다리는 것이 포함됩니다.
  • 이 작품은 많은 I / O 작업이 한 번에 일어나는 포함 하거나 하나 또는 다른 작업을 수행려고 할 때 더의 I / O 작업이 일어나고.

Async를 사용하면 여러 작업을 병렬로 설정하고 나머지 애플리케이션을 차단하지 않고도 효율적으로 반복 할 수 있습니다.

비동기로 잘 작동하는 작업의 몇 가지 예 :

  • 위에서 설명한 웹 스크래핑.
  • 네트워크 서비스 (예 : 웹 서버 또는 프레임 워크).
  • 값을 반환하는 데 오랜 시간이 걸리는 여러 소스의 결과를 조정하는 프로그램 (예 : 동시 데이터베이스 쿼리).

비동기 프로그래밍은 멀티 스레딩 또는 멀티 프로세싱과 다르다는 점에 유의해야합니다. 비동기 작업은 모두 동일한 스레드에서 실행되지만 필요에 따라 서로 양보하므로 많은 종류의 작업에 대해 스레딩 또는 다중 처리보다 비동기 작업이 더 효율적입니다. (아래에서 자세히 설명합니다.)

Python asyncawaitasyncio

Python은 최근 비동기 작업을 만들기 위해 두 개의 키워드 async및을 추가 await했습니다. 다음 스크립트를 고려하십시오.

def get_server_status (server_addr) # 잠재적으로 장기 실행되는 작업 ... return server_status def server_ops () results = [] results.append (get_server_status ( 'addr1.server') results.append (get_server_status ( 'addr2.server') return 결과 

동일한 스크립트의 비동기 버전 (작동하지 않고 구문 작동 방식에 대한 아이디어를 제공하기에 충분 함)은 다음과 같을 수 있습니다.

async def get_server_status (server_addr) # 잠재적으로 장기 실행되는 작업 ... return server_status async def server_ops () results = [] results.append (await get_server_status ( 'addr1.server') results.append (await get_server_status ( 'addr2. 서버 ') 결과 반환 

async키워드 접두사가 붙은 함수는 코 루틴 이라고도하는 비동기 함수가됩니다 . 코 루틴은 일반 함수와 다르게 작동합니다.

  • 코 루틴은 다른 키워드 await를 사용하여 코 루틴이 차단없이 다른 코 루틴의 결과를 기다릴 수 있습니다. awaited 코 루틴 에서 결과가 나올 때까지 Python은 실행중인 다른 코 루틴간에 자유롭게 전환합니다.
  • 코 루틴은 다른 함수 에서만 호출 할 있습니다 async. 스크립트 본문에서 실행 server_ops()하거나있는 get_server_status()그대로 실행 하면 결과를 얻을 수 없습니다. 직접 사용할 수없는 Python 코 루틴 객체를 얻게됩니다.

따라서 async비동기 함수에서 함수를 호출 할 수없고 async함수를 직접 실행할 수없는 경우 어떻게 사용합니까? 답변 : asyncio라이브러리 async와 나머지 Python 을 연결 하는 라이브러리 를 사용합니다 .

Python asyncawaitasyncio예제

여기에 하나를 사용하여 응용 프로그램을 긁는 웹을 쓸 수있는 방법에 (다시, 기능 만 설명되지 않음) 예입니다 asyncasyncio. 이 스크립트는 URL 목록을 가져 와서 async외부 라이브러리 ( read_from_site_async()) 에서 함수 의 여러 인스턴스를 사용 하여 다운로드하고 결과를 집계합니다.

web_scraping_library에서 asyncio 가져 오기 import read_from_site_async async def main (url_list) : return await asyncio.gather (* [read_from_site_async (_) for _ in url_list]) urls = [ '//site1.com','//othersite.com', '//newsite.com'] 결과 = asyncio.run (main (urls)) print (결과) 

위의 예에서는 두 가지 일반적인 asyncio기능을 사용 합니다.

  • asyncio.run() is used to launch an async function from the non-asynchronous part of our code, and thus kick off all of the progam’s async activities. (This is how we run main().)
  • asyncio.gather() takes one or more async-decorated functions (in this case, several instances of read_from_site_async() from our hypothetical web-scraping library), runs them all, and waits for all of the results to come in.

The idea here is, we start the read operation for all of the sites at once, then gather the results as they arrive (hence asyncio.gather()). We don’t wait for any one operation to complete before moving onto the next one.

Components of Python async apps

We’ve already mentioned how Python async apps use coroutines as their main ingredient, drawing on the asyncio library to run them. A few other elements are also key to asynchronous applications in Python:

Event loops

The asyncio library creates and manages event loops, the mechanisms that run coroutines until they complete. Only one event loop should be running at a time in a Python process, if only to make it easier for the programmer keep track of what goes into it.

Tasks

When you submit a coroutine to an event loop for processing, you can get back a Task object, which provides a way to control the behavior of the coroutine from outside the event loop. If you need to cancel the running task, for instance, you can do that by calling the task’s .cancel() method.

Here is a slightly different version of the site-scraper script that shows the event loop and tasks at work:

import asyncio from web_scraping_library import read_from_site_async tasks = [] async def main(url_list): for n in url_list: tasks.append(asyncio.create_task(read_from_site_async(n))) print (tasks) return await asyncio.gather(*tasks) urls = ['//site1.com','//othersite.com','//newsite.com'] loop = asyncio.get_event_loop() results = loop.run_until_complete(main(urls)) print (results) 

This script uses the event loop and task objects more explicitly.

  • The .get_event_loop() method provides us with an object that lets us control the event loop directly, by submitting async functions to it programmatically via .run_until_complete(). In the previous script, we could only run a single top-level async function, using asyncio.run(). By the way, .run_until_complete() does exactly what it says: It runs all of the supplied tasks until they’re done, then returns their results in a single batch.
  • The .create_task() method takes a function to run, including its parameters, and gives us back a Task object to run it. Here we submit each URL as a separate Task to the event loop, and store the Task objects in a list. Note that we can only do this inside the event loop—that is, inside an async function.

How much control you need over the event loop and its tasks will depend on how complex the application is that you’re building. If you just want to submit a set of fixed jobs to run concurrently, as with our web scraper, you won’t need a whole lot of control—just enough to launch jobs and gather the results. 

By contrast, if you’re creating a full-blown web framework, you’ll want far more control over the behavior of the coroutines and the event loop. For instance, you may need to shut down the event loop gracefully in the event of an application crash, or run tasks in a threadsafe manner if you’re calling the event loop from another thread.

Async vs. threading vs. multiprocessing

At this point you may be wondering, why use async instead of threads or multiprocessing, both of which have been long available in Python?

First, there is a key difference between async and threads or multiprocessing, even apart from how those things are implemented in Python. Async is about concurrency, while threads and multiprocessing are about parallelism. Concurrency involves dividing time efficiently among multiple tasks at once—e.g., checking your email while waiting for a register at the grocery store. Parallelism involves multiple agents processing multiple tasks side by side—e.g., having five separate registers open at the grocery store.

Most of the time, async is a good substitute for threading as threading is implemented in Python. This is because Python doesn’t use OS threads but its own cooperative threads, where only one thread is ever running at a time in the interpreter. In comparison to cooperative threads, async provides some key advantages:

  • Async functions are far more lightweight than threads. Tens of thousands of asynchronous operations running at once will have far less overhead than tens of thousands of threads.
  • The structure of async code makes it easier to reason about where tasks pick up and leave off. This means data races and thread safety are less of an issue. Because all tasks in the async event loop run in a single thread, it’s easier for Python (and the developer) to serialize how they access objects in memory.
  • Async operations can be cancelled and manipulated more readily than threads. The Task object we get back from asyncio.create_task() provides us with a handy way to do this.

Multiprocessing in Python, on the other hand, is best for jobs that are heavily CPU-bound rather than I/O-bound. Async actually works hand-in-hand with multiprocessing, as you can use asyncio.run_in_executor() to delegate CPU-intensive jobs to a process pool from a central process, without blocking that central process.

Next steps with Python async

The best first thing to do is build a few, simple async apps of your own. Good examples abound now that asynchronous programming in Python has undergone a few versions and had a couple of years to settle down and become more widely used. The official documentation for asyncio is worth reading over to see what it offers, even if you don’t plan to make use of all of its functions.

또한 점점 늘어나는 비동기식 라이브러리 및 미들웨어를 탐색 할 수 있으며, 이들 중 다수는 데이터베이스 커넥터, 네트워크 프로토콜 등의 비동기식 비 차단 버전을 제공합니다. aio-libs저장소 등과 같은 몇 가지 중요한 것들,이 aiohittp웹 액세스 라이브러리를. 또한 async키워드를 사용하여 라이브러리에 대한 Python 패키지 색인을 검색 할 가치가 있습니다 . 비동기 프로그래밍과 같은 것을 배우는 가장 좋은 방법은 다른 사람들이 어떻게 사용했는지 확인하는 것입니다.