[python] 0에서 1까지 코루틴 학습, 코루틴에 대한 철저한 이해, 리소스 소모가 증가하지 않는 경우, 멀티스레딩에 가까운 효과


머리말

다중 프로세스와 다중 스레드는 실제 프로그래밍에서 많이 사용되어 왔으며, 이 글의 목적은 코루틴을 학습한 경험과 경험을 기록하고, 그것을 해내기 위해 노력하는 글입니다.


코루틴의 이점에 대해 말할 것도 없이 I/O 집약적인 무기라고 할 수 있습니다.사실 코루틴은 IO 집약적인 작업에 대한 또 다른 옵션이 있습니다. 코루틴(micro-thread, 영문명 Coroutine)은 단일 스레드에서 실행되는 "동시성"입니다. 멀티 스레드와 비교하여 코루틴의 주요 장점은 멀티 스레드 간의 전환 오버헤드를 절약하고 더 높은 성능을 얻을 수 있다는 것입니다. 운영 효율성. Python의 비동기 IO 모듈 asyncio는 기본 코루틴 모듈입니다.
코루틴 전환은 스레드 전환과 다르며 프로그램 자체에 의해 제어되며 전환 오버헤드가 없습니다. 코루틴은 모두 동일한 스레드에서 실행되기 때문에 다중 스레드 잠금 메커니즘이 필요하지 않으므로 동시에 데이터에 액세스하는 데 문제가 없으며 실행 효율성이 다중 스레드보다 훨씬 높습니다.

1. 파이썬 생성기

1.1 파이썬 생성기 개요

세 가지 개념: 반복/반복자/반복 가능한 객체
생성기란 무엇입니까?
제너레이터도 반복 가능한 객체입니다.

[num for num in range(10)]
(num for num in range(10) )

위는 목록이고 아래는 생성기입니다.
여기에 이미지 설명 삽입

발전기의 특징

  1. 반복이 완료되면 마지막 것을 가리키며 다시는 반복되지 않습니다.목록과 다릅니다.
    여기에 이미지 설명 삽입
  2. 다른 메모리 크기를 차지합니다.
import sys
sys.getsizeof(10)
sys.getsizeof("Hello World")
sys.getsizeof(l)
sys.getsizeof(gen)
gen = (num for num in range(10000))
l = [num for num in range(10000)]

sys.getsizeof(l)
sys.getsizeof(gen)

여기에 이미지 설명 삽입
생성기가 차지하는 메모리는 거의 변하지 않지만 목록은 더 많은 요소로 커집니다!

생성기는 목록과 매우 유사하며 동일한 반복 방법을 사용하며 메모리 모델은 메모리 공간을 절약하고 생성기는 한 번만 순회할 수 있습니다. 지연 계산을 구현할 수 있으며 생성기 코드가 더 간결합니다.

1.2 키워드 수익률/수익률

Php, js, python C++ 모두 이 키워드를 가지고 있는데, 이는 비교적 진보된 문법 현상입니다.
yield는 함수에서만 사용할 수 있습니다.

def func():
	print("Hello World!");

type(func) 출력 <class 'function'>

def func():
	print("Hello World!");
	yield()

func()를 호출하세요.
여기에 이미지 설명 삽입
유형(func())에 주의하세요. 그는 생성기를 반환합니다! ! !
여기에 이미지 설명 삽입
일반 함수 호출은 원래 함수가 일반 함수의 실행을 반환하지 않기 때문에 None을 반환합니다.
yield는 함수를 생성기로 바꿉니다.

def func():
	for i in range(10):
		print(i)


def func2():
	for i in range(10):
		yield i

여기에 이미지 설명 삽입

for i in func2():
	print(i)

0에서 9까지 10개의 숫자도 생성됩니다.

1.3 다음/보내기 기능

다중 프로세스 및 다중 스레드는 운영 체제의 기능을 반영하는 반면 코루틴은 프로그래머의 프로세스 제어 기능을 반영합니다. 다음 예를 보면 두 작업자 A와 B가 번갈아 가며 두 작업을 시뮬레이션하고 단일 스레드에서 다중 스레드와 유사한 기능을 구현합니다.

import time

def task1():
    while True:
        yield "<甲>也累了,让<乙>工作一会儿"
        time.sleep(1)
        print("<甲>工作了一段时间.....")


def task2(t):
    next(t)
    while True:
        print("-----------------------------------")
        print("<乙>工作了一段时间.....")
        time.sleep(2)
        print("<乙>累了,让<甲>工作一会儿....")
        ret = t.send(None)
        print(ret)
    t.close()

if __name__ == '__main__':
    t = task1()
    task2(t)

산출:

<乙>工作了一段时间.....
<乙>累了,让<甲>工作一会儿....
<甲>工作了一段时间.....
<甲>也累了,让<乙>工作一会儿
-----------------------------------
<乙>工作了一段时间.....
<乙>累了,让<甲>工作一会儿....
<甲>工作了一段时间.....
<甲>也累了,让<乙>工作一会儿
-----------------------------------
<乙>工作了一段时间.....
<乙>累了,让<甲>工作一会儿....
<甲>工作了一段时间.....
<甲>也累了,让<乙>工作一会儿
-----------------------------------
<乙>工作了一段时间.....

질문 B의 명령문이 먼저 실행되고 A가 실행되는 이유를 생각하십시오.
t = task1()은 생성기를 반환합니다. task2(t), next(t)의 연산은 실행을 위해 task1에 들어가게 하고, 수율을 만나면 멈추고, 이때 next(t)의 반환 값은 <A>도 지쳤으니 <B>가 일하게 하라. 잠시 후
task2의 while True를 입력하고 t.send(None)를 만날 때까지 task1로 실행 권한이 반전되고 다음 yield 문장이 실행됩니다.
여기로 보내기는 아무 것도 전달할 수 없습니다.
초기에 Python은 생성기를 만들기 위한 yield 키워드를 제공했습니다. 즉, yield를 포함하는 함수는 제너레이터입니다!
yield의 문법 규칙은 다음과 같습니다. 여기 yield에서 함수의 실행을 일시 중단하고, next() 메서드에 의해 다시 호출될 때까지 yield 이후에 표현식의 값을 반환합니다(기본값은 None). 마지막으로 일시중지된 수익 코드 .
각 생성기는 send() 메서드를 구현하여 생성기 내부의 yield 문에 대한 데이터를 보낼 수 있습니다. 이때 yield 문은 더 이상 yield xxxx의 형태만 있는 것이 아니라 var = yield xxxx의 대입 형태도 가능합니다. 동시에 두 가지 기능이 있습니다. 하나는 일시 중지 및 함수 반환이고 다른 하나는 외부 send() 메서드에서 보낸 값을 수신하고 함수를 다시 활성화하고 이 값을 var 변수에 할당하는 것입니다!

def simple_coroutine():
    print('-> 启动协程')
    y = 10
    x = yield y
    print('-> 协程接收到了x的值:', x)

my_coro = simple_coroutine()
ret = next(my_coro)
print(ret)
my_coro.send(100)
-> 启动协程
10
-> 协程接收到了x的值: 1000
Traceback (most recent call last):
  File "c:\Users\jianming_ge\Desktop\碳汇算法第一版\carbon_code\单线程模拟多线程.py", line 54, in <module>
    my_coro.send(1000)
StopIteration

코루틴은 다음 네 가지 상태 중 하나일 수 있습니다. 현재 상태는 inspect 모듈로 가져올 수 있으며 다음 문자열 중 하나를 반환하는 inspect.getgeneratorstate(…) 메서드를 사용하여 확인할 수 있습니다.

실행을 시작하기 위해 대기 중인 'GEN_CREATED'.

'GEN_RUNNING' 코루틴이 실행 중입니다.

'GEN_SUSPENDED'는 yield 식에서 일시 중단됩니다.

'GEN_CLOSED' 실행이 종료되었습니다.

send() 메서드의 매개변수가 중단된 yield 표현식의 값이 되기 때문에 send() 메서드는 my_coro.send(10)와 같이 코루틴이 중단된 경우에만 호출할 수 있습니다. 그러나 코루틴이 아직 활성화되지 않은 상태(상태가 'GEN_CREATED')인 경우 즉시 None 이외의 값을 보내면 TypeError가 발생합니다. 따라서 항상 next(my_coro)를 호출하여 코루틴을 먼저 활성화해야 합니다(my_coro.send(None)를 호출할 수도 있음). 이 프로세스를 사전 활성화라고 합니다.

send() 메서드 외에도 실제로 throw() 및 close() 메서드가 있습니다.

generator.throw(exc_type[, exc_value[, traceback]])
일시 중지된 yield 표현식에서 생성기가 지정된 예외를 throw하도록 합니다. 제너레이터가 던져진 예외를 처리하면 코드는 다음 yield 표현식으로 실행되고 생성된 값은 generator.throw() 메서드의 반환 값이 됩니다. 생성기가 발생된 예외를 처리하지 않으면 예외가 호출자의 컨텍스트까지 올라갑니다.

generator.close()는
제너레이터가 일시 중지된 수율 표현식에서 GeneratorExit 예외를 발생시킵니다. 제너레이터가 이 예외를 처리하지 않거나 StopIteration 예외(일반적으로 끝까지 실행됨을 나타냄)를 발생시키는 경우 호출자는 오류를 보고하지 않습니다. GeneratorExit 예외가 수신되면 생성기는 값을 생성하지 않아야 합니다. 그렇지 않으면 인터프리터가 RuntimeError 예외를 발생시킵니다. 제너레이터가 던진 다른 예외는 호출자에게 버블링됩니다.

1.4 정지 인터레이션 비정상

보통 파이썬 프로그래밍을 할 때 일반적으로 예외에 주의를 기울이지 않지만 제너레이터를 학습할 때는 프로그래밍을 위해 이 stopinteration을 사용해야 합니다.

1.5 생성기를 사용하여 생산자-소비자 모델 구현

1.6 제너레이터와 코루틴의 관계

2. 제너레이터 코루틴 스케줄러

3.python 이벤트 기반 프로그래밍

4. 코루틴 스케줄러 구현

5. 파이썬 코루틴 생태계

@asyncio.coroutine 및
@asyncio.coroutine의 수율: asyncio 모듈의 데코레이터로 생성기를 코루틴으로 선언하는 데 사용됩니다.
다음 코드에서는 코루틴 display_date(num, loop)를 만든 다음 키워드 yield from을 사용하여 코루틴 asyncio.sleep(2)()의 반환 결과를 기다립니다. 그리고 기다리는 2초 동안 asyncio.sleep(2)이 결과를 반환할 때까지 CPU의 실행 권한을 포기합니다. asyncio.sleep(2)는 실제로 2초가 걸리는 IO 읽기 및 쓰기 작업을 시뮬레이트합니다.

import asyncio
import datetime

@asyncio.coroutine  # 声明一个协程
def display_date(num, loop):
    end_time = loop.time() + 10.0
    while True:
        print("Loop: {} Time: {}".format(num, datetime.datetime.now()))
        if (loop.time() + 1.0) >= end_time:
            break
        yield from asyncio.sleep(2)  # 阻塞直到协程sleep(2)返回结果
loop = asyncio.get_event_loop()  # 获取一个event_loop
tasks = [display_date(1, loop), display_date(2, loop)]
loop.run_until_complete(asyncio.gather(*tasks))  # "阻塞"直到所有的tasks完成
loop.close()

Python 3.5는 async/await 키워드를 도입하여 코루틴을 보다 직접적으로 지원합니다. 위의 코드는 다음과 같이 다시 작성할 수 있습니다. @asyncio.coroutine 대신 async를 사용하고 yield from 대신 await를 사용하면 코드가 더 간결하고 읽기 쉬워집니다. Python 디자인의 관점에서 async/await는 코루틴이 생성기와 독립적으로 존재하도록 허용하며 더 이상 yield 구문을 사용하지 않습니다.

import asyncio
import datetime

async def display_date(num, loop):      # 注意这一行的写法
    end_time = loop.time() + 10.0
    while True:
        print("Loop: {} Time: {}".format(num, datetime.datetime.now()))
        if (loop.time() + 1.0) >= end_time:
            break
        await asyncio.sleep(2)  # 阻塞直到协程sleep(2)返回结果

loop = asyncio.get_event_loop()  # 获取一个event_loop
tasks = [display_date(1, loop), display_date(2, loop)]
loop.run_until_complete(asyncio.gather(*tasks))  # "阻塞"直到所有的tasks完成
loop.close()

asyncio 모듈
asyncio의 사용은 세 단계로 나눌 수 있습니다.

이벤트 루프 생성
루프 모드 지정 및 실행
루프 닫기
일반적으로 우리는 루프를 생성하기 위해 asyncio.get_event_loop() 메서드를 사용합니다.

루프를 실행하는 방법에는 두 가지가 있습니다. 하나는 run_until_complete() 메서드를 호출하는 것이고 다른 하나는 run_forever() 메서드를 호출하는 것입니다. run_until_complete()에는 내장된 add_done_callback 콜백 함수가 있고 run_forever()는 add_done_callback()을 사용자 정의할 수 있습니다. 구체적인 차이점은 다음 두 가지 예를 참조하세요.

run_until_complete() 메서드를 사용합니다.

import asyncio

async def func(future):
    await asyncio.sleep(1)
    future.set_result('Future is done!')

if __name__ == '__main__':

    loop = asyncio.get_event_loop()
    future = asyncio.Future()
    asyncio.ensure_future(func(future))
    print(loop.is_running())   # 查看当前状态时循环是否已经启动
    loop.run_until_complete(future)
    print(future.result())
    loop.close()

run_forever() 메서드를 사용합니다.

import asyncio

async def func(future):
    await asyncio.sleep(1)
    future.set_result('Future is done!')

def call_result(future):
    print(future.result())
    loop.stop()

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    future = asyncio.Future()
    asyncio.ensure_future(func(future))
    future.add_done_callback(call_result)        # 注意这行
    try:
        loop.run_forever()
    finally:
        loop.close()

Guess you like

Origin blog.csdn.net/weixin_40293999/article/details/130478060