그린렛 대.스레드
나는 이벤트와 그린렛이 처음입니다.저는 그들과 함께 일하는 방법에 대한 좋은 문서를 찾았지만, 어떤 것도 제가 그린렛을 사용하는 방법과 시기에 대한 정당화를 해주지 않았습니다!
- 그들이 정말 잘하는 것은 무엇입니까?
- 프록시 서버에서 사용하는 것이 좋은 생각입니까?
- 스레드가 아닌 이유는 무엇입니까?
제가 확신할 수 없는 것은 그들이 기본적으로 공동 루틴이라면 어떻게 우리에게 동시성을 제공할 수 있는지입니다.
그린릿은 동시성을 제공하지만 병렬성은 제공하지 않습니다.동시성은 코드가 다른 코드와 독립적으로 실행될 수 있는 경우입니다.병렬 처리는 동시에 코드를 실행하는 것입니다.병렬 처리는 사용자 공간에서 수행해야 할 작업이 많은 경우 특히 유용하며, 일반적으로 CPU가 많이 사용되는 작업입니다.동시성은 여러 부품을 보다 쉽게 병렬로 예약하고 관리할 수 있도록 하여 문제를 분리하는 데 유용합니다.
그린렛은 한 소켓과의 상호 작용이 다른 소켓과의 상호 작용과 독립적으로 발생할 수 있는 네트워크 프로그래밍에서 정말로 빛을 발합니다.이것은 동시성의 전형적인 예입니다.각 그린릿은 고유한 컨텍스트에서 실행되므로 스레드 없이 동기식 API를 계속 사용할 수 있습니다.이는 스레드가 가상 메모리 및 커널 오버헤드 측면에서 매우 비싸기 때문에 스레드로 동시에 수행할 수 있는 작업이 훨씬 적기 때문입니다.또한 Python에서의 스레드화는 GIL로 인해 평소보다 더 비싸고 제한적입니다.동시성에 대한 대안은 일반적으로 Twisted, libevent, libuv, node.js 등과 같은 프로젝트로, 모든 코드가 동일한 실행 컨텍스트를 공유하고 이벤트 핸들러를 등록합니다.
요청을 처리할 때는 독립적으로 실행할 수 있으므로 프록시를 작성할 때는 grevent와 같은 적절한 네트워킹 지원이 포함된 greenlet을 사용하는 것이 좋습니다.
Greenlet은 제가 앞에서 제시한 이유에 대한 동시성을 제공합니다.동시성은 병렬성이 아닙니다.일반적으로 현재 스레드를 차단하는 호출에 대해 이벤트 등록을 숨기고 예약을 수행함으로써 gevent와 같은 프로젝트는 비동기 API를 변경할 필요 없이 동시성을 노출하고 시스템 비용을 크게 절감합니다.
위의 @TemporalBeing의 답변을 수정하면, 그린렛은 스레드보다 "빠르지 않고" 동시성 문제를 해결하기 위해 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
모두가 Python을 사용한 비블로킹 IO에 대해 오해하고 있는 것은 Python 인터프리터가 네트워크 연결 자체가 IO를 반환할 수 있는 것보다 더 빠르게 소켓에서 결과를 검색하는 작업에 참여할 수 있다는 믿음입니다.어떤 경우에는 이것이 확실히 사실이지만, 사람들이 생각하는 것만큼 자주 사실이 아닙니다. 왜냐하면 파이썬 인터프리터는 정말 정말 느리기 때문입니다.여기 제 블로그 게시물에서 저는 매우 간단한 것들에 대해서도 데이터베이스나 DNS 서버와 같은 것들에 대한 빠르고 정확한 네트워크 액세스를 처리하는 경우, 이러한 서비스는 Python 코드가 수천 개의 연결을 처리할 수 있는 것보다 훨씬 더 빨리 돌아올 수 있다는 것을 보여주는 그래픽 프로필을 보여줍니다.
@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에 상당한 차이가 있음을 알 수 있습니다.
이것은 분석하기에 충분히 흥미롭습니다.다음은 그린릿과 멀티프로세싱 풀, 멀티스레드의 성능을 비교하는 코드입니다.
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
나는 그 그린렛이 멀티스레딩 라이브러리와 달리 GIL에 구속되지 않는다고 주장한다고 생각합니다.게다가, Greenlet doc은 그것이 네트워크 운영을 위한 것이라고 말합니다.네트워크 집약적인 작업의 경우 스레드 전환이 가능하며 멀티스레딩 접근 방식이 매우 빠르다는 것을 알 수 있습니다.또한 python의 공식 라이브러리를 사용하는 것이 항상 선호됩니다. 윈도우에 greenlet을 설치하려고 시도하다가 dll 종속성 문제가 발생하여 Linux VM에서 이 테스트를 실행했습니다.어떤 기계에서든 실행되기를 바라는 마음으로 항상 코드를 작성하도록 노력하세요.
언급URL : https://stackoverflow.com/questions/15556718/greenlet-vs-threads
'programing' 카테고리의 다른 글
'WHERE' 절 sql에서 다른 값으로 폴백하는 방법은 무엇입니까? (0) | 2023.07.22 |
---|---|
임시 정의 정보 (0) | 2023.07.22 |
ASP.NET 그리드 보기에서 부울을 예/아니오로 변환할 수 있습니까? (0) | 2023.07.17 |
판다 - 사전에서 데이터 프레임에 새 열 추가 (0) | 2023.07.17 |
오라클에서 'as' 키워드를 사용하여 테이블에 별칭을 지정하는 방법은 무엇입니까? (0) | 2023.07.17 |