전역 통역사 잠금이란 무엇이며 왜 문제입니까?
파이썬에서 GIL을 제거하는 데 많은 소음이 들었으며, 그것이 왜 그렇게 중요한지 이해하고 싶습니다. 나는 컴파일러 나 인터프리터를 직접 작성하지 않았으므로 세부 사항에 소홀하지 마십시오. 아마 이해해야합니다.
전역 통역사 잠금이란 무엇이며 왜 문제입니까?
파이썬에서 GIL을 제거하는 데 많은 소음이 들었으며, 그것이 왜 그렇게 중요한지 이해하고 싶습니다. 나는 컴파일러 나 인터프리터를 직접 작성하지 않았으므로 세부 사항에 소홀하지 마십시오. 아마 이해해야합니다.
답변:
Python의 GIL은 다른 스레드에서 인터프리터 내부로의 액세스를 직렬화하기위한 것입니다. 다중 코어 시스템에서 이는 다중 스레드가 다중 코어를 효과적으로 사용할 수 없음을 의미합니다. (GIL이이 문제로 이어지지 않았다면, 대부분의 사람들은 GIL에 신경 쓰지 않을 것입니다. 멀티 코어 시스템의 보급이 증가함에 따라 GIL이 문제로만 제기되고 있습니다. 자세히 이해하려면, 당신이 볼 수있는 이 비디오 또는 모양을 슬라이드 세트 . 정보가 너무 많을 수도 있지만 세부 정보를 요청했습니다 :-)
파이썬의 GIL은 실제로 참조 구현 인 CPython의 문제 일뿐입니다. Jython과 IronPython에는 GIL이 없습니다. Python 개발자는 C 확장을 작성하지 않으면 일반적으로 GIL을 사용하지 않습니다. C 확장 작성자는 확장에서 I / O를 차단할 때 GIL을 해제해야하므로 Python 프로세스의 다른 스레드가 실행될 수 있습니다.
regex
, lxml
, numpy
모듈. Cython은 사용자 정의 코드로 GIL을 릴리스 할 수 있습니다.b2a_bin(data)
실제로 서로의 데이터를 건드리지 않는 여러 스레드가 있다고 가정하십시오 . 그것들은 가능한 한 독립적으로 실행되어야합니다. 함수를 호출하기 위해 획득해야하는 "전역 잠금"이있는 경우 병목 현상이 발생할 수 있습니다. 처음에 여러 스레드를 사용하면 많은 이점을 얻지 못할 수 있습니다.
현실 세계에 비유하자면 : 커피 머그잔 하나만있는 회사에서 일하는 100 명의 개발자를 상상해보십시오. 대부분의 개발자는 코딩 대신 커피를 기다리는 데 시간을 할애합니다.
이 중 어느 것도 파이썬에 국한되지 않습니다. 처음에 파이썬이 GIL을 필요로 한 것에 대한 세부 사항을 모르겠습니다. 그러나 일반적인 개념에 대한 더 나은 아이디어가 있기를 바랍니다.
파이썬 GIL이 제공하는 것을 먼저 이해합시다 :
모든 작업 / 명령은 인터프리터에서 실행됩니다. GIL은 인터프리터가 특정 시점에 단일 스레드에 의해 유지되도록합니다 . 여러 스레드가있는 파이썬 프로그램은 단일 인터프리터에서 작동합니다. 특정 시점에이 인터프리터는 단일 스레드에 의해 유지됩니다. 그것은 인터프리터를 들고에만 스레드가되는 것을 의미 실행 에 시간의 순간 .
왜 그게 문제입니까?
컴퓨터에 여러 개의 코어 / 프로세서가있을 수 있습니다. 또한 여러 개의 코어를 사용하면 여러 스레드가 동시에 실행될 수 있습니다. 즉, 여러 스레드가 특정 순간에 실행될 수 있습니다. . 그러나 인터프리터는 단일 스레드로 유지되므로 다른 스레드는 코어에 액세스하더라도 아무 것도 수행하지 않습니다. 따라서 현재 인터프리터를 보유하고있는 스레드가 사용하는 코어 인 단일 코어 만 사용되기 때문에 여러 코어가 제공하는 이점이 없습니다. 따라서 단일 스레드 프로그램 인 것처럼 프로그램을 실행하는 데 시간이 오래 걸립니다.
그러나 I / O, 이미지 처리 및 NumPy 번호 크 런칭과 같은 잠재적으로 차단되거나 오래 실행되는 작업은 GIL 외부에서 발생합니다. 여기 에서 찍은 . 따라서 이러한 작업의 경우 GIL이 있음에도 불구하고 멀티 스레드 작업이 단일 스레드 작업보다 빠릅니다. 따라서 GIL이 항상 병목 현상이되는 것은 아닙니다.
편집 : GIL은 CPython의 구현 세부 사항입니다. IronPython과 Jython에는 GIL이 없으므로 실제로 PyPy와 Jython을 사용한 적이 없으며 확실하지 않은 멀티 스레딩 프로그램이 가능해야합니다.
파이썬은 단어의 진정한 의미에서 멀티 스레딩을 허용하지 않습니다. 멀티 스레딩 패키지가 있지만 코드 속도를 높이기 위해 멀티 스레드를 원할 경우 일반적으로 사용하지 않는 것이 좋습니다. 파이썬에는 Global Interpreter Lock (GIL)이라는 구문이 있습니다.
https://www.youtube.com/watch?v=ph374fJqFPE
GIL은 한 번에 하나의 '스레드'만 실행할 수 있도록합니다. 스레드는 GIL을 획득하고 약간의 작업을 수행 한 다음 GIL을 다음 스레드로 전달합니다. 이것은 사람의 눈에 매우 빠르게 발생하므로 스레드가 병렬로 실행되는 것처럼 보일 수 있지만 실제로는 동일한 CPU 코어를 사용하여 교대로 진행됩니다. 이 모든 GIL 전달은 실행에 오버 헤드를 추가합니다. 즉, 코드를 빠르게 실행하려면 스레딩 패키지를 사용하는 것이 좋지 않습니다.
파이썬의 스레딩 패키지를 사용해야하는 이유가 있습니다. 동시에 몇 가지 작업을 수행하고 효율성이 중요하지 않은 경우 완전히 훌륭하고 편리합니다. 또는 IO와 같은 무언가를 기다려야하는 코드를 실행하는 경우 많은 의미가 있습니다. 그러나 스레딩 라이브러리에서는 추가 CPU 코어를 사용할 수 없습니다.
멀티 스레딩은 멀티 프로세싱을 통해 운영 체제에 아웃소싱하거나, Python 코드 (예 : Spark 또는 Hadoop)를 호출하는 외부 응용 프로그램 또는 Python 코드가 호출하는 일부 코드 (예 : Python을 가질 수 있음) 코드는 비싼 멀티 스레드 작업을 수행하는 C 함수를 호출합니다).
두 개의 스레드가 동일한 변수에 액세스 할 때마다 문제가 있습니다. 예를 들어 C ++에서 문제를 피하는 방법은 두 개의 스레드가 동시에 객체의 setter에 들어 가지 않도록 뮤텍스 잠금을 정의하는 것입니다.
파이썬에서는 멀티 스레딩이 가능하지만 하나의 파이썬 명령어보다 세분화 된 수준으로 두 개의 스레드를 동시에 실행할 수는 없습니다. 실행중인 스레드는 GIL이라는 전역 잠금을 얻습니다.
즉, 멀티 코어 프로세서를 활용하기 위해 멀티 스레드 코드를 작성하기 시작하면 성능이 향상되지 않습니다. 일반적인 해결 방법은 멀티 프로세스로 구성됩니다.
예를 들어 C로 작성한 메소드 안에 있다면 GIL을 해제 할 수 있습니다.
GIL의 사용은 파이썬 고유의 것이 아니라 가장 일반적인 CPython을 포함한 일부 인터프리터에 고유합니다. (#edited, 주석 참조)
GIL 문제는 여전히 Python 3000에서 유효합니다.
파이썬 3.7 문서
또한 파이썬 threading
문서 에서 다음 인용문을 강조하고 싶습니다 .
CPython 구현 세부 사항 : CPython에서는 Global Interpreter Lock으로 인해 한 번에 하나의 스레드 만 Python 코드를 실행할 수 있습니다 (특정 성능 지향 라이브러리가이 한계를 극복 할 수 있음에도 불구하고). 응용 프로그램에서 멀티 코어 컴퓨터의 계산 리소스를 더 잘 활용하려면
multiprocessing
또는 을 사용하는 것이 좋습니다concurrent.futures.ProcessPoolExecutor
. 그러나 여러 I / O 바운드 작업을 동시에 실행하려는 경우 스레딩은 여전히 적절한 모델입니다.
이것은 GILary 항목에global interpreter lock
연결되어 GIL이 파이썬의 스레드 병렬 처리가 CPU 바운드 작업에 적합하지 않음을 나타냅니다 .
CPython 인터프리터가 한 번에 하나의 스레드 만 Python 바이트 코드를 실행하도록하기 위해 사용하는 메커니즘입니다. 이것은 dict과 같은 중요한 내장 유형을 포함하여 객체 모델을 동시 액세스로부터 암시 적으로 안전하게하여 CPython 구현을 단순화합니다. 전체 인터프리터를 잠그면 멀티 프로세서 시스템이 제공하는 많은 병렬 처리를 희생하면서 인터프리터를 멀티 스레딩하기가 더 쉬워집니다.
그러나 표준 또는 타사의 일부 확장 모듈은 압축 또는 해싱과 같이 계산 집약적 인 작업을 수행 할 때 GIL을 해제하도록 설계되었습니다. 또한 I / O를 수행 할 때 항상 GIL이 해제됩니다.
"단일 스레드"인터프리터 (보다 세분화 된 수준으로 공유 데이터를 잠그는)를 작성하려는 과거의 노력은 일반적인 단일 프로세서 케이스에서 성능이 저하 되었기 때문에 성공하지 못했습니다. 이 성능 문제를 극복하면 구현이 훨씬 복잡해져 유지 관리 비용이 높아질 것으로 생각됩니다.
이 인용문은 또한 dicts와 변수 할당이 CPython 구현 세부 사항으로 스레드로부터 안전하다는 것을 암시합니다.
다음으로 패키지 문서는multiprocessing
프로세스를 생성하고 GIL을 극복하면서 GIL을 극복하는 방법을 설명합니다 threading
.
멀티 프로세싱은 스레딩 모듈과 유사한 API를 사용하여 스폰 프로세스를 지원하는 패키지입니다. 멀티 프로세싱 패키지는 로컬 및 원격 동시성을 모두 제공하여 스레드 대신 하위 프로세스를 사용하여 글로벌 인터프리터 잠금을 효과적으로 회피합니다. 이로 인해 멀티 프로세싱 모듈을 통해 프로그래머는 주어진 머신에서 여러 프로세서를 완전히 활용할 수 있습니다. Unix와 Windows 모두에서 실행됩니다.
그리고 문서concurrent.futures.ProcessPoolExecutor
multiprocessing
는 백엔드로 사용한다고 설명합니다 .
ProcessPoolExecutor 클래스는 프로세스 풀을 사용하여 호출을 비동기 적으로 실행하는 Executor 서브 클래스입니다. ProcessPoolExecutor는 멀티 프로세싱 모듈을 사용하므로 Global Interpreter Lock을 회피 할 수 있지만 피클 가능한 오브젝트 만 실행하고 리턴 할 수 있습니다.
프로세스 대신 스레드ThreadPoolExecutor
를 사용 하는 다른 기본 클래스와 대조되어야 합니다.
ThreadPoolExecutor는 스레드 풀을 사용하여 호출을 비동기 적으로 실행하는 Executor 서브 클래스입니다.
여기서 우리는 ThreadPoolExecutor
I / O 바운드 작업에만 적합하고 ProcessPoolExecutor
CPU 바운드 작업도 처리 할 수 있다고 결론을 내 렸습니다 .
다음 질문은 왜 GIL이 먼저 존재하는지 묻습니다. 왜 글로벌 통역사 잠금이 필요한가?
공정 대 스레드 실험
에서 스레딩 파이썬 대 멀티 파이썬에서 스레드 대 과정의 실험 분석을 완료했습니다.
결과의 빠른 미리보기 :
파이썬 (CPython 및 기타)이 GIL을 사용하는 이유
에서 http://wiki.python.org/moin/GlobalInterpreterLock
CPython에서 글로벌 인터프리터 잠금 (GIL)은 여러 원시 스레드가 한 번에 Python 바이트 코드를 실행하지 못하게하는 뮤텍스입니다. 이 잠금은 주로 CPython의 메모리 관리가 스레드로부터 안전하지 않기 때문에 필요합니다.
파이썬에서 제거하는 방법?
루아와 마찬가지로 파이썬은 여러 VM을 시작할 수 있지만 파이썬은 그렇게하지 않습니다. 다른 이유가있을 것 같습니다.
Numpy 또는 다른 python 확장 라이브러리에서 GIL을 다른 스레드로 해제하면 전체 프로그램의 효율성이 향상 될 수 있습니다.
Visual Effects에 대한 책 멀티 스레딩의 예제를 공유하고 싶습니다. 여기 전형적인 데드락 상황이 있습니다.
static void MyCallback(const Context &context){
Auto<Lock> lock(GetMyMutexFromContext(context));
...
EvalMyPythonString(str); //A function that takes the GIL
...
}
이제 시퀀스의 이벤트가 교착 상태를 초래하는 것을 고려하십시오.
╔═══╦════════════════════════════════════════╦══════════════════════════════════════╗
║ ║ Main Thread ║ Other Thread ║
╠═══╬════════════════════════════════════════╬══════════════════════════════════════╣
║ 1 ║ Python Command acquires GIL ║ Work started ║
║ 2 ║ Computation requested ║ MyCallback runs and acquires MyMutex ║
║ 3 ║ ║ MyCallback now waits for GIL ║
║ 4 ║ MyCallback runs and waits for MyMutex ║ waiting for GIL ║
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝