Python GIL은 무엇이고, 어떻게 작동하며, gunicorn에 어떤 영향을 미치는지 알아보세요.
제작 환경을 위해 어떤 Gunicorn 작업자 유형을 선택해야 하나요?
Python에는 하나의 스레드만 실행하도록 허용하는 전역 잠금(GIL)이 있습니다(예: 바이트코드 해석). 제 생각에는 Python 서비스를 최적화하려면 Python이 동시성을 처리하는 방법을 이해하는 것이 필수적입니다.
Python과 gunicorn은 동시성을 처리하는 다양한 방법을 제공하며 모든 사용 사례를 포괄하는 마법의 총알은 없으므로 각 옵션의 옵션, 장단점 및 장점을 이해하는 것이 가장 좋습니다.
Gunicorn은 "작업자 유형"이라는 개념으로 이러한 다양한 옵션을 노출합니다. 각 유형은 특정 사용 사례 세트에 적합합니다.
이것은 유일한 동시성 옵션이 요청을 병렬로 처리할 N 프로세스를 포크하는 가장 간단한 유형의 작업입니다.
잘 작동할 수 있지만 오버헤드(예: 메모리 및 CPU 컨텍스트 전환 등)가 많이 발생하고 대부분의 요청 시간이 I/O를 기다리는 경우 확장이 잘 되지 않습니다.
gthread 작업자는 프로세스당 N개의 스레드를 생성할 수 있도록 하여 이를 개선합니다. 이렇게 하면 더 많은 코드 인스턴스를 동시에 실행할 수 있으므로 I/O 성능이 향상됩니다. 이는 GIL의 영향을 받는 네 가지 중 유일한 것입니다.
eventlet/gevent 작업자는 경량 사용자 스레드(녹색 스레드, greenlet 등이라고도 함)를 실행하여 gthread 모델을 더욱 개선하려고 시도합니다.
이를 통해 시스템 스레드에 비해 훨씬 적은 비용으로 수천 개의 Greenlet을 가질 수 있습니다. 또 다른 차이점은 선제적 작업 모델이 아닌 협업 작업 모델을 따르며, 막힐 때까지 중단 없이 작업할 수 있다는 점입니다. 먼저 요청을 처리할 때 gthread 작업자 스레드의 동작과 이것이 GIL의 영향을 받는 방식을 분석합니다.
각 요청이 하나의 프로세스에서 직접 제공되는 동기화와 달리 gthread를 사용하면 각 프로세스에 N 스레드가 있어 여러 프로세스의 오버헤드 없이 더 효과적으로 확장할 수 있습니다. 동일한 프로세스에서 여러 스레드를 실행하고 있으므로 GIL은 스레드가 병렬로 실행되는 것을 방지합니다.
GIL은 프로세스나 특별한 스레드가 아닙니다. 이는 각 프로세스 내에서 하나의 스레드만 실행되도록 보장하는 뮤텍스로 액세스가 보호되는 부울 변수일 뿐입니다. 작동 방식은 위 이미지에서 볼 수 있습니다. 이 예에서는 동시에 실행되는 2개의 시스템 스레드가 있고 각 스레드는 1개의 요청을 처리하는 것을 볼 수 있습니다. 프로세스는 다음과 같습니다.
프로세스를 사용하지 않고 동시성을 높이는 또 다른 옵션은 greenlet을 사용하는 것입니다. 이 작업자는 동시성을 높이기 위해 "시스템 스레드" 대신 "사용자 스레드"를 생성합니다.
이는 GIL의 영향을 받지 않는다는 의미이지만 CPU에 의해 병렬로 예약될 수 없기 때문에 여전히 병렬성을 높일 수 없다는 의미이기도 합니다.
이 경우 Greenlet 유형의 작업자를 갖는 것이 이상적이지 않다는 것은 명백합니다. 결국 두 번째 요청은 첫 번째 요청이 완료될 때까지 대기한 다음 다시 I/O를 기다리게 됩니다.
이러한 시나리오에서 Greenlet 협업 모델은 컨텍스트 전환에 시간을 낭비하지 않고 여러 시스템 스레드를 실행하는 오버헤드를 피하기 때문에 정말 빛납니다.
이 기사 마지막에 있는 벤치마크 테스트에서 이를 목격하게 될 것입니다. 이제 다음 질문이 필요합니다.
이러한 질문에 답하려면 모니터링하여 필요한 측정항목을 수집한 다음 동일한 측정항목에 대해 맞춤형 벤치마크를 실행해야 합니다. 실제 사용 패턴과 상관 관계가 없는 합성 벤치마크를 실행하는 것은 소용이 없습니다. 아래 그래프는 다양한 시나리오에 대한 대기 시간 및 처리량 측정 항목을 보여 주며 모든 것이 어떻게 함께 작동하는지에 대한 아이디어를 제공합니다.
여기서 GIL 스레드 전환 간격/시간 초과 변경이 요청 대기 시간에 어떤 영향을 미치는지 확인할 수 있습니다. 예상한 대로 스위칭 간격이 줄어들수록 IO 대기 시간이 좋아집니다. 이는 CPU 바인딩 스레드가 GIL을 더 자주 릴리스하고 다른 스레드가 작업을 완료할 수 있도록 하기 때문에 발생합니다.
하지만 이것이 만병통치약은 아닙니다. 스위치 간격을 줄이면 CPU 바인딩된 스레드를 완료하는 데 시간이 더 오래 걸립니다. 또한 지속적인 스레드 전환으로 인한 오버헤드 증가로 인해 전체 대기 시간이 증가하고 시간 초과가 감소하는 것을 확인할 수 있습니다. 직접 해보고 싶다면 다음 코드를 사용하여 전환 간격을 변경할 수 있습니다.
전반적으로 벤치마크가 직관을 반영하는 것을 볼 수 있습니다. GIL 바인딩 스레드와 greenlet이 작동하는 방식에 대한 이전 분석에서 파생되었습니다.
gthread는 장기 실행 스레드를 강제로 해제하는 전환 간격으로 인해 IO 바운드 요청에 대한 평균 대기 시간이 더 좋습니다.
gevent CPU 바인딩 요청은 다른 요청을 처리하기 위해 중단되지 않기 때문에 gthread보다 대기 시간이 더 좋습니다.
여기 결과는 gevent가 gthread보다 처리량이 더 좋다는 이전 직관을 반영합니다. 이러한 벤치마크는 수행 중인 작업 유형에 따라 크게 달라지며 반드시 사용 사례로 직접 변환되지 않을 수도 있습니다.
이 벤치마크의 주요 목표는 요청을 처리할 각 CPU 코어를 최대화하기 위해 무엇을 테스트하고 측정해야 하는지에 대한 지침을 제공하는 것입니다.
모든 gunicorn 작업자는 실행할 프로세스 수를 지정할 수 있으므로 각 프로세스가 동시 연결을 처리하는 방식이 변경됩니다. 따라서 동일한 수의 작업자를 사용하여 테스트를 공정하게 만드십시오. 이제 벤치마크에서 수집한 데이터를 사용하여 이전 질문에 답해 보겠습니다.
그렇죠. 그러나 대부분의 워크로드에서는 이것이 획기적인 변화가 아닙니다.
혼합 I/O와 CPU로 작업할 때 gevent/eventlet과 gthread 중에서 어떻게 선택합니까? 보시다시피, ghtread는 CPU 집약적인 작업이 많을 때 더 나은 동시성을 허용하는 경향이 있습니다.
벤치마크에서 프로덕션과 유사한 동작을 시뮬레이션할 수 있는 한 최대 성능을 명확하게 확인한 다음 스레드가 너무 많아 성능이 저하되기 시작합니다.
GIL을 피하기 위해 동기화 워커를 사용하고 분기된 프로세스 수를 늘려야 할까요?
I/O가 거의 0이 아닌 이상 프로세스만으로 확장하는 것은 최선의 선택이 아닙니다.
코루틴/Greenlet은 스레드 간 인터럽트 및 컨텍스트 전환을 방지하므로 CPU 효율성을 향상시킬 수 있습니다. 코루틴은 처리량을 위해 대기 시간을 교환합니다.
IO와 CPU 바인딩 엔드포인트를 혼합하면 코루틴으로 인해 예측할 수 없는 지연 시간이 더 발생할 수 있습니다. CPU 바인딩 엔드포인트는 다른 수신 요청을 처리하기 위해 중단되지 않습니다. 시간을 들여 gunicorn을 올바르게 구성하면 GIL은 문제가 되지 않습니다.
위 내용은 하나의 기사로 Gunicorn과 Python GIL 이해하기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!