"concurrent.futures" --- 병렬 작업 실행하기
*******************************************

버전 3.2에 추가.

**소스 코드:** Lib/concurrent/futures/thread.py와
Lib/concurrent/futures/process.py

======================================================================

"concurrent.futures" 모듈은 비동기적으로 콜러블을 실행하는 고수준 인터
페이스를 제공합니다.

비동기 실행은 ("ThreadPoolExecutor"를 사용해서) 스레드나
("ProcessPoolExecutor"를 사용해서) 별도의 프로세스로 수행 할 수 있습니
다. 둘 다 추상 "Executor" 클래스로 정의된 것과 같은 인터페이스를 구현
합니다.


Executor 객체
=============

class concurrent.futures.Executor

   비동기적으로 호출을 실행하는 메서드를 제공하는 추상 클래스입니다.
   직접 사용해서는 안 되며, 구체적인 하위 클래스를 통해 사용해야 합니
   다.

      submit(fn, /, *args, **kwargs)

         Schedules the callable, *fn*, to be executed as "fn(*args,
         **kwargs)" and returns a "Future" object representing the
         execution of the callable.

            with ThreadPoolExecutor(max_workers=1) as executor:
                future = executor.submit(pow, 323, 1235)
                print(future.result())

      map(func, *iterables, timeout=None, chunksize=1)

         "map(func, *iterables)" 과 비슷하지만, 다음과 같은 차이가 있
         습니다:

         * *iterables* 는 느긋하게 처리되는 것이 아니라 즉시 수집됩니
           다.

         * *func* 는 비동기적으로 실행되며 *func* 에 대한 여러 호출이
           동시에 이루어질 수 있습니다.

         반환된 이터레이터는 "__next__()" 가 호출되었을 때,
         "Executor.map()" 에 대한 최초 호출에서 *timeout* 초 후에도 결
         과를 사용할 수 없는 경우 "concurrent.futures.TimeoutError" 를
         발생시킵니다. *timeout* 은 int 또는 float가 될 수 있습니다.
         *timeout* 이 지정되지 않았거나 "None" 인 경우, 대기 시간에는
         제한이 없습니다.

         *func* 호출이 예외를 일으키면, 값을 이터레이터에서 꺼낼 때 해
         당 예외가 발생합니다.

         "ProcessPoolExecutor"를 사용할 때, 이 메서드는 *iterables* 를
         다수의 덩어리로 잘라서 별도의 작업으로 풀에 제출합니다. 이러
         한 덩어리의 (대략적인) 크기는 *chunksize* 를 양의 정수로 설정
         하여 지정할 수 있습니다. 매우 긴 이터러블의 경우 *chunksize*
         에 큰 값을 사용하면 기본 크기인 1에 비해 성능이 크게 향상될
         수 있습니다. "ThreadPoolExecutor" 의 경우, *chunksize* 는 아
         무런 효과가 없습니다.

         버전 3.5에서 변경: *chunksize* 인자가 추가되었습니다.

      shutdown(wait=True, *, cancel_futures=False)

         현재 계류 중인 퓨처가 실행 완료될 때, 사용 중인 모든 자원을
         해제해야 한다는 것을 실행기에 알립니다. 종료(shutdown) 후에
         이루어지는 "Executor.submit()" 과 "Executor.map()" 호출은
         "RuntimeError" 를 발생시킵니다.

         *wait* 가 "True" 면, 계류 중인 모든 퓨처가 실행을 마치고 실행
         기와 관련된 자원이 해제될 때까지 이 메서드는 돌아오지 않습니
         다. *wait* 가 "False" 면, 이 메서드는 즉시 돌아오고 실행기와
         연관된 자원은 계류 중인 모든 퓨처가 실행을 마칠 때 해제됩니다
         . *wait* 의 값과 관계없이, 모든 계류 중인 퓨처가 실행을 마칠
         때까지 전체 파이썬 프로그램이 종료되지 않습니다.

         *cancel_futures*가 "True"이면, 이 메서드는 실행기가 실행을 시
         작시키지 않은 계류 중인 모든 퓨처를 취소합니다.
         *cancel_futures*의 값과 관계없이 완료되었거나 실행 중인 퓨처
         는 취소되지 않습니다.

         *cancel_futures*와 *wait*가 모두 "True"이면, 이 메서드가 반환
         하기 전에 실행기가 실행을 시작한 모든 퓨처가 완료됩니다. 나머
         지 퓨처는 취소됩니다.

         "with" 문을 사용하여 "Executor"를 종료시키면
         ("Executor.shutdown()" 를 *wait* 값 "True" 로 호출한 것처럼
         대기합니다), 이 메서드를 명시적으로 호출할 필요가 없어집니다
         .:

            import shutil
            with ThreadPoolExecutor(max_workers=4) as e:
                e.submit(shutil.copy, 'src1.txt', 'dest1.txt')
                e.submit(shutil.copy, 'src2.txt', 'dest2.txt')
                e.submit(shutil.copy, 'src3.txt', 'dest3.txt')
                e.submit(shutil.copy, 'src4.txt', 'dest4.txt')

         버전 3.9에서 변경: *cancel_futures*를 추가했습니다.


ThreadPoolExecutor
==================

"ThreadPoolExecutor" 는 스레드 풀을 사용하여 호출을 비동기적으로 실행
하는 "Executor" 서브 클래스입니다.

"Future"와 관련된 콜러블 객체가 다른 "Future" 의 결과를 기다릴 때 교착
상태가 발생할 수 있습니다. 예를 들면:

   import time
   def wait_on_b():
       time.sleep(5)
       print(b.result())  # b will never complete because it is waiting on a.
       return 5

   def wait_on_a():
       time.sleep(5)
       print(a.result())  # a will never complete because it is waiting on b.
       return 6


   executor = ThreadPoolExecutor(max_workers=2)
   a = executor.submit(wait_on_b)
   b = executor.submit(wait_on_a)

그리고:

   def wait_on_future():
       f = executor.submit(pow, 5, 2)
       # This will never complete because there is only one worker thread and
       # it is executing this function.
       print(f.result())

   executor = ThreadPoolExecutor(max_workers=1)
   executor.submit(wait_on_future)

class concurrent.futures.ThreadPoolExecutor(max_workers=None, thread_name_prefix='', initializer=None, initargs=())

   최대 *max_workers* 스레드의 풀을 사용하여 호출을 비동기적으로 실행
   하는 "Executor" 서브 클래스.

   *initializer* 는 각 작업자 스레드의 시작 부분에서 호출되는 선택적
   콜러블입니다; *initargs* 는 initializer에 전달되는 인자들의 튜플입
   니다. *initializer* 가 예외를 발생시키는 경우, 현재 계류 중인 모든
   작업과 풀에 추가로 작업을 제출하려는 시도는 "BrokenThreadPool" 을
   발생시킵니다.

   버전 3.5에서 변경: *max_workers* 가 "None" 이거나 주어지지 않았다면
   , 기본값으로 기계의 프로세서 수에 "5" 를 곱한 값을 사용합니다.
   "ThreadPoolExecutor" 가 CPU 작업보다는 I/O를 동시에 진행하는데 자주
   쓰이고, 작업자의 수가 "ProcessPoolExecutor" 보다 많아야 한다고 가정
   하고 있습니다.

   버전 3.6에 추가: *thread_name_prefix* 인자가 추가되어, 디버깅 편의
   를 위해 사용자가 풀이 만드는 작업자 스레드의 "threading.Thread" 이
   름을 제어 할 수 있습니다.

   버전 3.7에서 변경: *initializer* 및 *initargs* 인자가 추가되었습니
   다.

   버전 3.8에서 변경: *max_workers*의 기본값은 "min(32, os.cpu_count()
   + 4)"로 변경됩니다. 이 기본값은 I/O 병목 작업을 위해 최소 5개의 작
   업자를 유지합니다. GIL을 반납하는 CPU 병목 작업을 위해 최대 32개의
   CPU 코어를 사용합니다. 또한 많은 코어를 가진 시스템에서 매우 큰 자
   원을 묵시적으로 사용하는 것을 방지합니다.ThreadPoolExecutor는 이제
   *max_workers* 작업자 스레드를 시작하기 전에 유휴 작업자 스레드를 재
   사용합니다.


ThreadPoolExecutor 예제
-----------------------

   import concurrent.futures
   import urllib.request

   URLS = ['http://www.foxnews.com/',
           'http://www.cnn.com/',
           'http://europe.wsj.com/',
           'http://www.bbc.co.uk/',
           'http://nonexistant-subdomain.python.org/']

   # Retrieve a single page and report the URL and contents
   def load_url(url, timeout):
       with urllib.request.urlopen(url, timeout=timeout) as conn:
           return conn.read()

   # We can use a with statement to ensure threads are cleaned up promptly
   with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
       # Start the load operations and mark each future with its URL
       future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
       for future in concurrent.futures.as_completed(future_to_url):
           url = future_to_url[future]
           try:
               data = future.result()
           except Exception as exc:
               print('%r generated an exception: %s' % (url, exc))
           else:
               print('%r page is %d bytes' % (url, len(data)))


ProcessPoolExecutor
===================

"ProcessPoolExecutor" 클래스는 프로세스 풀을 사용하여 호출을 비동기적
으로 실행하는 "Executor" 서브 클래스입니다. "ProcessPoolExecutor" 는
"multiprocessing" 모듈을 사용합니다. *전역 인터프리터 록* 을 피할 수
있도록 하지만, 오직 피클 가능한 객체만 실행되고 반환될 수 있음을 의미
합니다.

"__main__" 모듈은 작업자 서브 프로세스가 임포트 할 수 있어야 합니다.
즉, "ProcessPoolExecutor" 는 대화형 인터프리터에서 작동하지 않습니다.

"ProcessPoolExecutor" 에 제출된 콜러블에서 "Executor" 나 "Future" 메서
드를 호출하면 교착 상태가 발생합니다.

class concurrent.futures.ProcessPoolExecutor(max_workers=None, mp_context=None, initializer=None, initargs=())

   최대 *max_workers* 프로세스의 풀을 사용하여 호출을 비동기적으로 실
   행하는 "Executor" 서브 클래스. *max_workers* 가 "None" 이거나 주어
   지지 않았다면, 기계의 프로세서 수를 기본값으로 사용합니다.
   *max_workers* 가 "0" 보다 작거나 같으면 "ValueError" 가 발생합니다.
   윈도우에서, *max_workers*는 "61"보다 작거나 같아야 합니다. 그렇지
   않으면 "ValueError"가 발생합니다. *max_workers*가 "None" 이면, 더
   많은 프로세서를 사용할 수 있다 할지라도 선택된 기본값은 최대 "61"이
   될 것입니다. *mp_context* 는 multiprocessing 컨텍스트이거나 None일
   수 있습니다. 작업자들을 만드는데 사용될 것입니다. *mp_context* 가
   "None" 이거나 주어지지 않으면 기본 multiprocessing 컨텍스트가 사용
   됩니다.

   *initializer* 는 각 작업자 프로세스의 시작 부분에서 호출되는 선택적
   콜러블입니다; *initargs* 는 initializer에 전달되는 인자들의 튜플입
   니다. *initializer* 가 예외를 발생시키는 경우, 현재 계류 중인 모든
   작업과 풀에 추가로 작업을 제출하려는 시도는 "BrokenProcessPool" 을
   발생시킵니다.

   버전 3.3에서 변경: 작업자 프로세스 중 하나가 갑자기 종료되면,
   "BrokenProcessPool" 오류가 발생합니다. 이전에는, 동작이 정의되지 않
   았지만, 실행기나 그 퓨처에 대한 연산이 종종 멈추거나 교착 상태에 빠
   졌습니다.

   버전 3.7에서 변경: *mp_context* 인자가 추가되어 사용자가 풀에서 만
   드는 작업자 프로세스의 시작 방법을 제어 할 수 있습니다
   .*initializer* 및 *initargs* 인자가 추가되었습니다.


ProcessPoolExecutor 예제
------------------------

   import concurrent.futures
   import math

   PRIMES = [
       112272535095293,
       112582705942171,
       112272535095293,
       115280095190773,
       115797848077099,
       1099726899285419]

   def is_prime(n):
       if n < 2:
           return False
       if n == 2:
           return True
       if n % 2 == 0:
           return False

       sqrt_n = int(math.floor(math.sqrt(n)))
       for i in range(3, sqrt_n + 1, 2):
           if n % i == 0:
               return False
       return True

   def main():
       with concurrent.futures.ProcessPoolExecutor() as executor:
           for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
               print('%d is prime: %s' % (number, prime))

   if __name__ == '__main__':
       main()


Future 객체
===========

"Future" 클래스는 콜러블 객체의 비동기 실행을 캡슐화합니다. "Future"
인스턴스는 "Executor.submit()" 에 의해 생성됩니다.

class concurrent.futures.Future

   콜러블 객체의 비동기 실행을 캡슐화합니다. "Future" 인스턴스는
   "Executor.submit()" 에 의해 생성되며 테스트를 제외하고는 직접 생성
   되어서는 안 됩니다.

      cancel()

         호출을 취소하려고 시도합니다. 호출이 현재 실행 중이거나 실행
         종료했고 취소할 수 없는 경우 메서드는 "False" 를 반환하고, 그
         렇지 않으면 호출이 취소되고 메서드는 "True" 를 반환합니다.

      cancelled()

         호출이 성공적으로 취소되었으면 "True" 를 반환합니다.

      running()

         호출이 현재 실행 중이고 취소할 수 없는 경우 "True" 를 반환합
         니다.

      done()

         호출이 성공적으로 취소되었거나 실행이 완료되었으면 "True" 를
         반환합니다.

      result(timeout=None)

         호출이 반환한 값을 돌려줍니다. 호출이 아직 완료되지 않는 경우
         , 이 메서드는 *timeout* 초까지 대기합니다. *timeout* 초 내에
         호출이 완료되지 않으면 "concurrent.futures.TimeoutError" 가
         발생합니다. *timeout* 은 int 또는 float가 될 수 있습니다.
         *timeout* 이 지정되지 않았거나 "None" 인 경우, 대기 시간에는
         제한이 없습니다.

         완료하기 전에 퓨처가 취소되면 "CancelledError" 가 발생합니다.

         If the call raised an exception, this method will raise the
         same exception.

      exception(timeout=None)

         호출이 일으킨 예외를 돌려줍니다. 호출이 아직 완료되지 않는 경
         우, 이 메서드는 *timeout* 초까지 대기합니다. *timeout* 초 내
         에 호출이 완료되지 않으면 "concurrent.futures.TimeoutError"
         가 발생합니다. *timeout* 은 int 또는 float가 될 수 있습니다.
         *timeout* 이 지정되지 않았거나 "None" 인 경우, 대기 시간에는
         제한이 없습니다.

         완료하기 전에 퓨처가 취소되면 "CancelledError" 가 발생합니다.

         호출이 예외 없이 완료되면, "None" 이 반환됩니다.

      add_done_callback(fn)

         콜러블 *fn* 을 퓨처에 연결합니다. *fn* 은 퓨처가 취소되거나
         실행이 종료될 때 퓨처를 유일한 인자로 호출됩니다.

         추가된 콜러블은 추가된 순서대로 호출되며, 항상 콜러블을 추가
         한 프로세스에 속하는 스레드에서 호출됩니다. 콜러블이
         "Exception" 서브 클래스를 발생시키면, 로그 되고 무시됩니다.
         콜러블이 "BaseException" 서브 클래스를 발생시키면, 동작은 정
         의되지 않습니다.

         퓨처가 이미 완료되었거나 취소된 경우 *fn* 이 즉시 호출됩니다.

   다음 "Future" 메서드는 단위 테스트와 "Executor" 의 구현을 위한 것입
   니다.

      set_running_or_notify_cancel()

         이 메서드는 "Future"와 관련된 작업을 실행하기 전에 "Executor"
         구현에 의해서만 호출되거나 단위 테스트에서만 호출되어야 합니
         다.

         메서드가 "False" 를 반환하면, "Future" 가 취소된 것입니다. 즉
         "Future.cancel()" 이 호출되었고 *True*를 반환했습니다.
         "Future" 완료를 기다리는 (즉, "as_completed()" 또는 "wait()"
         를 통해) 모든 스레드는 깨어납니다.

         메서드가 "True" 를 반환하면, "Future" 가 취소되지 않았고 실행
         상태로 진입했습니다. 즉 "Future.running()" 을 호출하면 *True*
         가 반환됩니다.

         이 메서드는 한 번만 호출 할 수 있으며, "Future.set_result()"
         또는 "Future.set_exception()" 이 호출 된 후에는 호출할 수 없
         습니다.

      set_result(result)

         "Future"와 관련된 작업 결과를 *result* 로 설정합니다.

         이 메서드는 "Executor" 구현과 단위 테스트에서만 사용해야 합니
         다.

         버전 3.8에서 변경: 이 메서드는 "Future"가 이미 완료되었으면
         "concurrent.futures.InvalidStateError"를 발생시킵니다.

      set_exception(exception)

         "Future"와 관련된 작업 결과를 "Exception" *exception* 으로 설
         정합니다.

         이 메서드는 "Executor" 구현과 단위 테스트에서만 사용해야 합니
         다.

         버전 3.8에서 변경: 이 메서드는 "Future"가 이미 완료되었으면
         "concurrent.futures.InvalidStateError"를 발생시킵니다.


모듈 함수
=========

concurrent.futures.wait(fs, timeout=None, return_when=ALL_COMPLETED)

   Wait for the "Future" instances (possibly created by different
   "Executor" instances) given by *fs* to complete. Duplicate futures
   given to *fs* are removed and will be returned only once. Returns a
   named 2-tuple of sets.  The first set, named "done", contains the
   futures that completed (finished or cancelled futures) before the
   wait completed.  The second set, named "not_done", contains the
   futures that did not complete (pending or running futures).

   *timeout* 은 반환하기 전에 대기 할 최대 시간(초)을 제어하는 데 사용
   할 수 있습니다. *timeout* 은 int 또는 float가 될 수 있습니다.
   *timeout* 이 지정되지 않았거나 "None" 인 경우, 대기 시간에는 제한이
   없습니다.

   *return_when* 은, 이 함수가 언제 반환되어야 하는지를 나타냅니다. 다
   음 상수 중 하나여야 합니다:

   +-------------------------------+------------------------------------------+
   | 상수                          | 설명                                     |
   |===============================|==========================================|
   | "FIRST_COMPLETED"             | 퓨처가 어느 하나라도 끝나거나 취소될 때  |
   |                               | 함수가 반환됩니다.                       |
   +-------------------------------+------------------------------------------+
   | "FIRST_EXCEPTION"             | 어느 한 퓨처가 예외를 일으켜 완료하면 함 |
   |                               | 수가 반환됩니다. 어떤 퓨처 도 예외를 발  |
   |                               | 생시키지 않으면 "ALL_COMPLETED"와 같습니 |
   |                               | 다.                                      |
   +-------------------------------+------------------------------------------+
   | "ALL_COMPLETED"               | 모든 퓨처가 끝나거나 취소되면 함수가 반  |
   |                               | 환됩니다.                                |
   +-------------------------------+------------------------------------------+

concurrent.futures.as_completed(fs, timeout=None)

   *fs* 로 주어진 여러 (서로 다른 "Executor" 인스턴스가 만든 것들도 가
   능합니다) 퓨처들이 완료되는 대로 (끝났거나 취소된 퓨처) 일드 하는
   "Future" 인스턴스의 이터레이터를 반환합니다. *fs* 에 중복된 퓨처가
   들어있으면 한 번만 반환됩니다. "as_completed()" 가 호출되기 전에 완
   료한 모든 퓨처들이 먼저 일드 됩니다. 반환된 이터레이터는,
   "__next__()" 가 호출되고, "as_completed()" 호출 시점으로부터
   *timeout* 초 후에 결과를 얻을 수 없는 경우
   "concurrent.futures.TimeoutError" 를 발생시킵니다. *timeout* 은 int
   또는 float가 될 수 있습니다. *timeout* 이 지정되지 않았거나 "None"
   인 경우, 대기 시간에는 제한이 없습니다.

더 보기:

  **PEP 3148** -- 퓨처 - 계산을 비동기적으로 실행
     파이썬 표준 라이브러리에 포함하기 위해, 이 기능을 설명한 제안.


예외 클래스
===========

exception concurrent.futures.CancelledError

   퓨처가 취소될 때 발생합니다.

exception concurrent.futures.TimeoutError

   퓨처 연산이 지정된 시간제한을 초과할 때 발생합니다.

exception concurrent.futures.BrokenExecutor

   "RuntimeError" 에서 파생됩니다, 이 예외 클래스는 어떤 이유로 실행기
   가 망가져서 새 작업을 제출하거나 실행할 수 없을 때 발생합니다.

   버전 3.7에 추가.

exception concurrent.futures.InvalidStateError

   퓨처에 현재 상태에서 허용되지 않는 연산이 수행될 때 발생합니다.

   버전 3.8에 추가.

exception concurrent.futures.thread.BrokenThreadPool

   "BrokenExecutor" 에서 파생됩니다, 이 예외 클래스는
   "ThreadPoolExecutor" 의 작업자 중 하나가 초기화에 실패했을 때 발생
   합니다.

   버전 3.7에 추가.

exception concurrent.futures.process.BrokenProcessPool

   "BrokenExecutor" 에서 파생됩니다 (예전에는 "RuntimeError"), 이 예외
   클래스는 "ProcessPoolExecutor" 의 작업자 중 하나가 깨끗하지 못한 방
   식으로 (예를 들어, 외부에서 강제 종료된 경우) 종료되었을 때 발생합
   니다.

   버전 3.3에 추가.
