나는 gevents와 greenlet을 처음 사용합니다. 나는 그들과 함께 일하는 방법에 대한 좋은 문서를 찾았지만, 언제 어떻게 Greenlet을 사용해야하는지에 대한 정당성을 제시하지 못했습니다!
- 그들은 정말로 무엇을 잘합니까?
- 프록시 서버에서 사용하는 것이 좋습니까?
- 왜 스레드하지 않습니까?
내가 확실하지 않은 것은 그들이 기본적으로 공동 루틴 인 경우 그들이 우리에게 동시성을 제공 할 수있는 방법입니다.
나는 gevents와 greenlet을 처음 사용합니다. 나는 그들과 함께 일하는 방법에 대한 좋은 문서를 찾았지만, 언제 어떻게 Greenlet을 사용해야하는지에 대한 정당성을 제시하지 못했습니다!
내가 확실하지 않은 것은 그들이 기본적으로 공동 루틴 인 경우 그들이 우리에게 동시성을 제공 할 수있는 방법입니다.
답변:
Greenlet은 동시성을 제공하지만 병렬 처리 는 제공 하지 않습니다 . 동시성은 코드가 다른 코드와 독립적으로 실행될 수있는 경우입니다. 병렬 처리는 동시 코드를 동시에 실행하는 것입니다. 병렬 처리는 사용자 공간에서 수행해야 할 작업이 많을 때 특히 유용하며 일반적으로 CPU가 많은 작업입니다. 동시성은 문제를 해결하는 데 유용하며, 다른 부분을보다 쉽게 병렬로 예약하고 관리 할 수 있습니다.
Greenlets는 한 소켓과의 상호 작용이 다른 소켓과의 상호 작용과 독립적으로 발생할 수있는 네트워크 프로그래밍에서 실제로 빛납니다. 이것은 동시성의 전형적인 예입니다. 각 그린 릿은 자체 컨텍스트에서 실행되므로 스레딩없이 동기식 API를 계속 사용할 수 있습니다. 스레드는 가상 메모리 및 커널 오버 헤드 측면에서 매우 비싸기 때문에 스레드로 달성 할 수있는 동시성이 훨씬 적기 때문에 좋습니다. 또한 Python의 스레딩은 GIL로 인해 평소보다 비싸고 제한적입니다. 동시성에 대한 대안은 일반적으로 모든 코드가 동일한 실행 컨텍스트를 공유하고 이벤트 핸들러를 등록하는 Twisted, libevent, libuv, node.js 등과 같은 프로젝트입니다.
요청 처리가 독립적으로 실행될 수 있고 그렇게 작성되어야하므로 프록시 작성에 그린 렛 (gevent를 통한 적절한 네트워킹 지원 포함)을 사용하는 것이 좋습니다.
Greenlet은 내가 이전에 제공 한 이유로 동시성을 제공합니다. 동시성은 병렬 처리가 아닙니다. gevent와 같은 프로젝트는 일반적으로 현재 스레드를 차단하는 호출에서 이벤트 등록을 숨기고 일정을 수행함으로써 비동기 API를 변경하지 않고도 시스템에 훨씬 적은 비용으로이 동시성을 노출합니다.
import hashlib def checksum_md5(filename): md5 = hashlib.md5() with open(filename,'rb') as f: for chunk in iter(lambda: f.read(8192), b''): md5.update(chunk) return md5.digest()
@Max의 답변을 취하고 스케일링과 관련성을 추가하면 차이점을 알 수 있습니다. 다음과 같이 채워지도록 URL을 변경하여이를 달성했습니다.
URLS_base = ['www.google.com', 'www.example.com', 'www.python.org', 'www.yahoo.com', 'www.ubc.ca', 'www.wikipedia.org']
URLS = []
for _ in range(10000):
for url in URLS_base:
URLS.append(url)
500을 갖기 전에 멀티 프로세스 버전을 삭제해야했습니다. 그러나 10,000 회 반복 :
Using gevent it took: 3.756914
-----------
Using multi-threading it took: 15.797028
따라서 gevent를 사용하여 I / O에 큰 차이가 있음을 알 수 있습니다.
위의 @TemporalBeing의 대답에 따르면, greenlet은 스레드보다 "빠르지"않으며 동시성 문제를 해결하기 위해 60000 스레드 를 생성하는 잘못된 프로그래밍 기술 이므로 작은 스레드 풀이 대신 적합합니다. 다음은보다 합리적인 비교입니다 ( 이 SO 게시물을 인용 한 사람들의 응답으로 내 레딧 게시물 에서).
import gevent
from gevent import socket as gsock
import socket as sock
import threading
from datetime import datetime
def timeit(fn, URLS):
t1 = datetime.now()
fn()
t2 = datetime.now()
print(
"%s / %d hostnames, %s seconds" % (
fn.__name__,
len(URLS),
(t2 - t1).total_seconds()
)
)
def run_gevent_without_a_timeout():
ip_numbers = []
def greenlet(domain_name):
ip_numbers.append(gsock.gethostbyname(domain_name))
jobs = [gevent.spawn(greenlet, domain_name) for domain_name in URLS]
gevent.joinall(jobs)
assert len(ip_numbers) == len(URLS)
def run_threads_correctly():
ip_numbers = []
def process():
while queue:
try:
domain_name = queue.pop()
except IndexError:
pass
else:
ip_numbers.append(sock.gethostbyname(domain_name))
threads = [threading.Thread(target=process) for i in range(50)]
queue = list(URLS)
for t in threads:
t.start()
for t in threads:
t.join()
assert len(ip_numbers) == len(URLS)
URLS_base = ['www.google.com', 'www.example.com', 'www.python.org',
'www.yahoo.com', 'www.ubc.ca', 'www.wikipedia.org']
for NUM in (5, 50, 500, 5000, 10000):
URLS = []
for _ in range(NUM):
for url in URLS_base:
URLS.append(url)
print("--------------------")
timeit(run_gevent_without_a_timeout, URLS)
timeit(run_threads_correctly, URLS)
결과는 다음과 같습니다.
--------------------
run_gevent_without_a_timeout / 30 hostnames, 0.044888 seconds
run_threads_correctly / 30 hostnames, 0.019389 seconds
--------------------
run_gevent_without_a_timeout / 300 hostnames, 0.186045 seconds
run_threads_correctly / 300 hostnames, 0.153808 seconds
--------------------
run_gevent_without_a_timeout / 3000 hostnames, 1.834089 seconds
run_threads_correctly / 3000 hostnames, 1.569523 seconds
--------------------
run_gevent_without_a_timeout / 30000 hostnames, 19.030259 seconds
run_threads_correctly / 30000 hostnames, 15.163603 seconds
--------------------
run_gevent_without_a_timeout / 60000 hostnames, 35.770358 seconds
run_threads_correctly / 60000 hostnames, 29.864083 seconds
파이썬으로 비 차단 IO에 대한 모든 사람들의 오해는 파이썬 인터프리터가 네트워크 연결 자체가 IO를 반환 할 수있는 것보다 소켓에서 결과를 더 빨리 검색하는 작업에 참여할 수 있다는 믿음입니다. 어떤 경우에는 이것이 사실이지만, 파이썬 인터프리터가 실제로 느리기 때문에 사람들이 생각하는 것처럼 거의 사실이 아닙니다. 내 블로그 게시물 here 에서는 데이터베이스 또는 DNS 서버와 같은 것들에 대한 선명하고 빠른 네트워크 액세스를 처리하는 경우 해당 서비스가 Python 코드보다 훨씬 빠르게 돌아올 수 있음을 보여주는 매우 간단한 그래픽 프로파일을 보여줍니다 수천 개의 연결에 참석할 수 있습니다.
이것은 분석하기에 충분히 흥미 롭습니다. 다음은 그린 릿과 멀티 프로세싱 풀 및 멀티 스레딩의 성능을 비교하는 코드입니다.
import gevent
from gevent import socket as gsock
import socket as sock
from multiprocessing import Pool
from threading import Thread
from datetime import datetime
class IpGetter(Thread):
def __init__(self, domain):
Thread.__init__(self)
self.domain = domain
def run(self):
self.ip = sock.gethostbyname(self.domain)
if __name__ == "__main__":
URLS = ['www.google.com', 'www.example.com', 'www.python.org', 'www.yahoo.com', 'www.ubc.ca', 'www.wikipedia.org']
t1 = datetime.now()
jobs = [gevent.spawn(gsock.gethostbyname, url) for url in URLS]
gevent.joinall(jobs, timeout=2)
t2 = datetime.now()
print "Using gevent it took: %s" % (t2-t1).total_seconds()
print "-----------"
t1 = datetime.now()
pool = Pool(len(URLS))
results = pool.map(sock.gethostbyname, URLS)
t2 = datetime.now()
pool.close()
print "Using multiprocessing it took: %s" % (t2-t1).total_seconds()
print "-----------"
t1 = datetime.now()
threads = []
for url in URLS:
t = IpGetter(url)
t.start()
threads.append(t)
for t in threads:
t.join()
t2 = datetime.now()
print "Using multi-threading it took: %s" % (t2-t1).total_seconds()
결과는 다음과 같습니다.
Using gevent it took: 0.083758
-----------
Using multiprocessing it took: 0.023633
-----------
Using multi-threading it took: 0.008327
Greenlet은 멀티 스레딩 라이브러리와 달리 GIL에 구속되지 않는다고 주장합니다. 또한 Greenlet doc은 네트워크 운영을위한 것이라고 말합니다. 네트워크 집약적 인 작업의 경우 스레드 전환이 양호하고 멀티 스레딩 방식이 매우 빠르다는 것을 알 수 있습니다. 또한 파이썬의 공식 라이브러리를 사용하는 것이 항상 바람직합니다. Windows에 greenlet 설치를 시도하고 dll 종속 문제가 발생하여 Linux vm 에서이 테스트를 실행했습니다. 항상 모든 컴퓨터에서 실행될 것이라는 희망으로 코드를 작성하십시오.
getsockbyname
OS 수준 (적어도 내 컴퓨터에서는)에서 결과 를 캐시합니다. 이전에 알 수 없거나 만료 된 DNS에서 호출되면 실제로 네트워크 쿼리를 수행하는데 시간이 걸릴 수 있습니다. 최근에 확인 된 호스트 이름을 호출하면 응답이 훨씬 빠르게 반환됩니다. 결과적으로 측정 방법에 결함이 있습니다. 이것은 이상한 결과를 설명합니다-gevent는 실제로 멀티 스레딩보다 훨씬 나을 수는 없습니다. 둘 다 VM 수준에서 실제로 평행하지는 않습니다.
using_gevent() 421.442985535ms using_multiprocessing() 394.540071487ms using_multithreading() 402.48298645ms