Greenlet Vs. 실


141

나는 gevents와 greenlet을 처음 사용합니다. 나는 그들과 함께 일하는 방법에 대한 좋은 문서를 찾았지만, 언제 어떻게 Greenlet을 사용해야하는지에 대한 정당성을 제시하지 못했습니다!

  • 그들은 정말로 무엇을 잘합니까?
  • 프록시 서버에서 사용하는 것이 좋습니까?
  • 왜 스레드하지 않습니까?

내가 확실하지 않은 것은 그들이 기본적으로 공동 루틴 인 경우 그들이 우리에게 동시성을 제공 할 수있는 방법입니다.


1
@Imran Java의 녹색 스레드에 관한 것입니다. 내 질문은 파이썬의 greenlet에 관한 것입니다. 뭔가 빠졌습니까?
Rsh

Afaik, 파이썬의 스레드는 실제로 전역 인터프리터 잠금으로 인해 실제로 동시 적이 지 않습니다. 따라서 두 솔루션의 오버 헤드를 비교하는 것으로 요약됩니다. 파이썬에는 여러 가지 구현이 있다는 것을 알고 있지만 모든 것에 적용되지는 않을 수 있습니다.
didierc

3
@didierc CPython (및 현재 PyPy)은 파이썬 (바이트) 코드 를 병렬로 해석하지 않습니다 (즉, 실제로 두 개의 별개의 CPU 코어에서 실제로 물리적으로 동시에). 그러나 파이썬 프로그램이 수행하는 모든 것이 GIL (일반적인 예제는 의도적으로 GIL을 해제하는 I / O 및 C 함수를 포함하는 시스템 콜)이며, a threading.Thread는 실제로 모든 파급 효과가있는 OS 스레드입니다. 그래서 그렇게 간단하지 않습니다. 그런데 자이 썬에는 GIL AFAIK가 없으며 PyPy도 제거하려고합니다.

답변:


204

Greenlet은 동시성을 제공하지만 병렬 처리 는 제공 하지 않습니다 . 동시성은 코드가 다른 코드와 독립적으로 실행될 수있는 경우입니다. 병렬 처리는 동시 코드를 동시에 실행하는 것입니다. 병렬 처리는 사용자 공간에서 수행해야 할 작업이 많을 때 특히 유용하며 일반적으로 CPU가 많은 작업입니다. 동시성은 문제를 해결하는 데 유용하며, 다른 부분을보다 쉽게 ​​병렬로 예약하고 관리 할 수 ​​있습니다.

Greenlets는 한 소켓과의 상호 작용이 다른 소켓과의 상호 작용과 독립적으로 발생할 수있는 네트워크 프로그래밍에서 실제로 빛납니다. 이것은 동시성의 전형적인 예입니다. 각 그린 릿은 자체 컨텍스트에서 실행되므로 스레딩없이 동기식 API를 계속 사용할 수 있습니다. 스레드는 가상 메모리 및 커널 오버 헤드 측면에서 매우 비싸기 때문에 스레드로 달성 할 수있는 동시성이 훨씬 적기 때문에 좋습니다. 또한 Python의 스레딩은 GIL로 인해 평소보다 비싸고 제한적입니다. 동시성에 대한 대안은 일반적으로 모든 코드가 동일한 실행 컨텍스트를 공유하고 이벤트 핸들러를 등록하는 Twisted, libevent, libuv, node.js 등과 같은 프로젝트입니다.

요청 처리가 독립적으로 실행될 수 있고 그렇게 작성되어야하므로 프록시 작성에 그린 렛 (gevent를 통한 적절한 네트워킹 지원 포함)을 사용하는 것이 좋습니다.

Greenlet은 내가 이전에 제공 한 이유로 동시성을 제공합니다. 동시성은 병렬 처리가 아닙니다. gevent와 같은 프로젝트는 일반적으로 현재 스레드를 차단하는 호출에서 이벤트 등록을 숨기고 일정을 수행함으로써 비동기 API를 변경하지 않고도 시스템에 훨씬 적은 비용으로이 동시성을 노출합니다.


1
고마워, 단지 두 가지 작은 질문 : 1)이 솔루션을 멀티 프로세싱과 결합하여 더 높은 처리량을 달성 할 수 있습니까? 2) 여전히 스레드를 사용하는 이유를 모르겠습니다. 파이썬 표준 라이브러리에서 동시성의 기본 구현으로 간주 할 수 있습니까?
Rsh

6
1) 그렇습니다. 이 작업을 조기에 수행해서는 안되지만이 질문의 범위를 벗어나는 여러 가지 요인으로 인해 여러 프로세스가 요청을 처리하도록하면 처리량이 높아집니다. 2) OS 스레드는 사전 예약되어 기본적으로 완전히 병렬화됩니다. 파이썬은 기본 스레딩 인터페이스를 제공하기 때문에 파이썬에서 기본값이며, 스레드는 최신 운영 체제에서 병렬 처리와 동시성 모두에 대해 가장 잘 지원되고 가장 낮은 공통 분모입니다.
Matt Joiner

6
스레드가 만족스럽지 않을 때까지 (보통 처리하는 동시 연결 수와 스레드 수 또는 GIL이 슬픔을 유발하기 때문에) 그린 릿을 사용해서는 안된다는 점을 언급해야합니다. 다른 옵션이없는 경우에만 가능합니다. Python 표준 라이브러리와 대부분의 타사 라이브러리는 스레드를 통해 동시성이 달성 될 것으로 기대 하므로 Greenlet을 통해 제공하면 이상한 동작이 발생할 수 있습니다.
Matt Joiner 2016 년

@ MatJoiner 나는 md5 합계를 계산하기 위해 큰 파일을 읽는 아래 함수를 가지고 있습니다. 이 경우 gevent를 사용하여 더 빨리 읽을 수있는 방법 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()
Soumya

18

@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에 큰 차이가 있음을 알 수 있습니다.


4
작업을 완료하기 위해 60000 원시 스레드 또는 프로세스를 생성하는 것은 전적으로 올바르지 않으며이 테스트는 아무 것도 표시하지 않습니다 (gevent.joinall () 호출에서 시간 초과를 제거 했습니까?). 약 50 스레드의 스레드 풀을 사용해보십시오. 내 대답을 참조하십시오 : stackoverflow.com/a/51932442/34549
zzzeek

9

위의 @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 코드보다 훨씬 빠르게 돌아올 수 있음을 보여주는 매우 간단한 그래픽 프로파일을 보여줍니다 수천 개의 연결에 참석할 수 있습니다.


8

이것은 분석하기에 충분히 흥미 롭습니다. 다음은 그린 릿과 멀티 프로세싱 풀 및 멀티 스레딩의 성능을 비교하는 코드입니다.

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 에서이 테스트를 실행했습니다. 항상 모든 컴퓨터에서 실행될 것이라는 희망으로 코드를 작성하십시오.


25
getsockbynameOS 수준 (적어도 내 컴퓨터에서는)에서 결과 를 캐시합니다. 이전에 알 수 없거나 만료 된 DNS에서 호출되면 실제로 네트워크 쿼리를 수행하는데 시간이 걸릴 수 있습니다. 최근에 확인 된 호스트 이름을 호출하면 응답이 훨씬 빠르게 반환됩니다. 결과적으로 측정 방법에 결함이 있습니다. 이것은 이상한 결과를 설명합니다-gevent는 실제로 멀티 스레딩보다 훨씬 나을 수는 없습니다. 둘 다 VM 수준에서 실제로 평행하지는 않습니다.
KT.

1
@KT. 그것은 훌륭한 지적입니다. 이 테스트를 여러 번 실행하고 좋은 그림을 얻으려면 수단, 모드 및 중앙값을 사용해야합니다. 라우터는 프로토콜에 대한 경로 경로를 캐시하고 경로 경로를 캐시하지 않는 경우 다른 DNS 경로 경로 트래픽에서 지연이 발생할 수 있습니다. 그리고 dns 서버는 많은 캐시를합니다. CPU 하드웨어가 대기 시간의 영향을받는 대신 CPU주기가 사용되는 time.clock ()을 사용하여 스레딩을 측정하는 것이 좋습니다. 이로 인해 다른 OS 서비스가 몰래 들어가고 측정 시간이 추가 될 수 있습니다.
DevPlayer

그리고 세 가지 테스트 사이에 OS 수준에서 dns 플러시를 실행할 수 있지만 다시 로컬 dns 캐싱의 잘못된 데이터 만 줄입니다.
DevPlayer

예. 이 정리 된 버전을 실행 : paste.ubuntu.com/p/pg3KTzT2FG 거의 같은 시간을 얻습니다 ...using_gevent() 421.442985535ms using_multiprocessing() 394.540071487ms using_multithreading() 402.48298645ms
sehe

OSX가 dns 캐싱을 수행하고 있다고 생각하지만 Linux에서는 "기본"이 아닙니다 : stackoverflow.com/a/11021207/34549 , 예. 낮은 수준의 동시성 그리 렛에서는 인터프리터 오버 헤드로 인해 훨씬 ​​나빠집니다.
zzzeek
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.