Giulio Franco가 말하는 것은 일반적으로 멀티 스레딩 대 멀티 프로세싱 에 대해 사실입니다 .
그러나 Python * 에는 추가 문제가 있습니다. 동일한 프로세스에있는 두 개의 스레드가 동시에 Python 코드를 실행하지 못하도록하는 Global Interpreter Lock이 있습니다. 즉, 8 개의 코어가 있고 8 개의 스레드를 사용하도록 코드를 변경하면 800 % CPU를 사용하고 8 배 더 빠르게 실행할 수 없습니다. 동일한 100 % CPU를 사용하고 동일한 속도로 실행됩니다. (실제로는 공유 데이터가 없더라도 스레딩으로 인한 추가 오버 헤드가 있기 때문에 약간 느리게 실행되지만 지금은 무시합니다.)
이에 대한 예외가 있습니다. 코드의 무거운 계산이 실제로 Python에서 발생하지 않지만 numpy 앱과 같이 적절한 GIL 처리를 수행하는 사용자 지정 C 코드가있는 일부 라이브러리에서는 스레딩을 통해 예상되는 성능 이점을 얻을 수 있습니다. 실행하고 대기하는 일부 하위 프로세스에서 무거운 계산을 수행하는 경우에도 마찬가지입니다.
더 중요한 것은 이것이 중요하지 않은 경우가 있다는 것입니다. 예를 들어 네트워크 서버는 네트워크에서 패킷을 읽는 데 대부분의 시간을 소비하고 GUI 앱은 사용자 이벤트를 기다리는 데 대부분의 시간을 소비합니다. 네트워크 서버 또는 GUI 앱에서 스레드를 사용하는 한 가지 이유는 주 스레드가 네트워크 패킷 또는 GUI 이벤트를 계속 서비스하는 것을 중지하지 않고 장기 실행 "백그라운드 작업"을 수행 할 수 있도록하기 위해서입니다. 그리고 그것은 파이썬 스레드에서 잘 작동합니다. (기술적 인 측면에서 이것은 파이썬 스레드가 코어 병렬성을 제공하지 않더라도 동시성을 제공함을 의미합니다.)
그러나 순수 Python으로 CPU 바인딩 된 프로그램을 작성하는 경우 더 많은 스레드를 사용하는 것은 일반적으로 도움이되지 않습니다.
별도의 프로세스를 사용하는 것은 각 프로세스가 자체적으로 별도의 GIL을 가지고 있기 때문에 GIL에 그러한 문제가 없습니다. 물론 다른 언어에서와 마찬가지로 스레드와 프로세스 간에는 동일한 트레이드 오프가 있습니다. 스레드간에 데이터를 공유하는 것보다 프로세스간에 데이터를 공유하는 것이 더 어렵고 비용이 많이 들며, 엄청난 수의 프로세스를 실행하거나 생성 및 삭제하는 데 비용이 많이들 수 있습니다. 하지만 GIL은 예를 들어 C 또는 Java에 대해 사실이 아닌 방식으로 프로세스에 대한 균형에 크게 무게를 둡니다. 따라서 C 또는 Java에서보다 Python에서 훨씬 더 자주 다중 처리를 사용하게됩니다.
한편, 파이썬의 "배터리 포함"철학은 좋은 소식을 가져다줍니다. 한 줄의 변경으로 스레드와 프로세스 사이를 앞뒤로 전환 할 수있는 코드를 작성하는 것은 매우 쉽습니다.
입력 및 출력을 제외하고 다른 작업 (또는 기본 프로그램)과 공유하지 않는 자체 포함 된 "작업"측면에서 코드를 디자인하는 경우 concurrent.futures
라이브러리를 사용하여 다음과 같이 스레드 풀을 중심으로 코드를 작성할 수 있습니다.
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
executor.submit(job, argument)
executor.map(some_function, collection_of_independent_things)
작업의 결과를 가져 와서 추가 작업에 전달할 수도 있고, 실행 순서 나 완료 순서 등을 기다릴 수도 있습니다. Future
자세한 내용 은 개체 섹션 을 참조하십시오.
이제 프로그램이 100 % CPU를 지속적으로 사용하고 스레드를 더 추가하면 속도가 느려지는 것으로 밝혀지면 GIL 문제가 발생하므로 프로세스로 전환해야합니다. 당신이해야 할 일은 첫 번째 줄을 변경하는 것입니다.
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:
유일한주의 사항은 작업의 인수와 반환 값이 피클 가능해야한다는 것입니다 (피클하는 데 너무 많은 시간이나 메모리가 걸리지 않아야 함). 일반적으로 이것은 문제가되지 않지만 때로는 문제가됩니다.
하지만 당신의 직업이 자립 할 수 없다면 어떨까요? 메시지 를 서로 전달 하는 작업의 관점에서 코드를 디자인 할 수 있다면 여전히 매우 쉽습니다. 풀 을 사용 threading.Thread
하거나 multiprocessing.Process
대신 풀 을 사용해야 할 수도 있습니다. 그리고 명시 적으로 queue.Queue
또는 multiprocessing.Queue
객체 를 생성해야합니다 . (파이프, 소켓, 무리가있는 파일 등 다른 많은 옵션이 있지만 요점은 Executor의 자동 마법이 불충분하면 수동으로 무언가 를해야 한다는 것 입니다.)
하지만 메시지 전달에 의존 할 수 없다면 어떨까요? 동일한 구조를 변경하고 서로의 변경 사항을 확인하기 위해 두 가지 작업이 필요한 경우 어떻게해야합니까? 이 경우 수동 동기화 (잠금, 세마포, 조건 등)를 수행해야하며 프로세스를 사용하려면 명시 적 공유 메모리 개체를 부팅해야합니다. 이것은 멀티 스레딩 (또는 멀티 프로세싱)이 어려워 질 때입니다. 피할 수 있다면 좋습니다. 할 수 없다면 누군가가 대답에 넣을 수있는 것보다 더 많이 읽어야 할 것입니다.
댓글에서 Python에서 스레드와 프로세스의 차이점을 알고 싶었습니다. 정말로, Giulio Franco의 답변과 저와 우리의 모든 링크를 읽으면 모든 것을 다루어야하지만 요약은 확실히 유용 할 것입니다.
- 스레드는 기본적으로 데이터를 공유합니다. 프로세스는 그렇지 않습니다.
- (1)의 결과로 프로세스간에 데이터를 전송하려면 일반적으로 데이터를 피클 링 및 언 피클 링해야합니다. **
- (1)의 또 다른 결과로, 프로세스간에 데이터를 직접 공유하려면 일반적으로 값, 배열 및
ctypes
유형 과 같은 저수준 형식으로 데이터를 넣어야합니다 .
- 프로세스에는 GIL이 적용되지 않습니다.
- 일부 플랫폼 (주로 Windows)에서는 프로세스를 만들고 제거하는 데 훨씬 많은 비용이 듭니다.
- 프로세스에 대한 몇 가지 추가 제한 사항이 있으며 그 중 일부는 플랫폼마다 다릅니다. 자세한 내용은 프로그래밍 지침 을 참조하십시오.
threading
모듈의 일부 기능이없는 multiprocessing
모듈을. ( multiprocessing.dummy
쓰레드 위에 누락 된 API의 대부분을 가져 오는 데 사용할 수 있습니다 . 또는 concurrent.futures
걱정하지 않고 같은 상위 수준 모듈을 사용할 수 있습니다 .)
* 실제로이 문제가있는 언어는 Python이 아니라 해당 언어의 "표준"구현 인 CPython입니다. 일부 다른 구현에는 Jython과 같은 GIL이 없습니다.
** 대부분의 비 Windows 플랫폼에서 수행 할 수있는 다중 처리 를 위해 포크 시작 방법을 사용하는 경우 각 자식 프로세스는 자식이 시작될 때 부모가 가지고 있던 모든 리소스를 가져옵니다. 이는 자식에게 데이터를 전달하는 또 다른 방법 일 수 있습니다.
Thread
모듈 (_thread
python 3.x에서 호출 됨)도 있습니다. 솔직히 말해서, 나 자신 ... 차이를 이해 적이