docs: def buffered_generator(source_gen, buffer_size=2, use_multiprocessing=False):
      r"""
      Generator that runs a slow source generator in a separate process.
  
      My generate function still seems faster on test cases.
      However, this function is more flexible in its compatability.
  
      Args:
          source_gen (iterable): slow generator
          buffer_size (int): the maximal number of items to pre-generate
              (length of the buffer) (default = 2)
          use_multiprocessing (bool): if False uses GIL-hindered threading
              instead of multiprocessing (defualt = False).
  
      Note:
          use_multiprocessing = True seems to freeze if passed in a generator
          built by six.moves.map.
  
      References:
          Taken from Sander Dieleman's data augmentation pipeline
          https://github.com/benanne/kaggle-ndsb/blob/11a66cdbddee16c69514b9530a727df0ac6e136f/buffering.py
  
      CommandLine:
          python -m utool.util_parallel --test-buffered_generator:0
          python -m utool.util_parallel --test-buffered_generator:1
  
      Ignore:
          >>> #functime = timeit.timeit(
          >>> # 'ut.is_prime(' + str(prime) + ')', setup='import utool as ut',
          >>> # number=500) / 1000.0
  
      Example:
          >>> # DISABLE_DOCTEST
          >>> # UNSTABLE_DOCTEST
          >>> from utool.util_parallel import *  # NOQA
          >>> import utool as ut
          >>> num = 2 ** 14
          >>> func = ut.is_prime
          >>> data = [38873] * num
          >>> data = list(range(num))
          >>> with ut.Timer('serial') as t1:
          ...     result1 = list(map(func, data))
          >>> with ut.Timer('ut.generate2') as t3:
          ...     result3 = list(ut.generate2(func, zip(data), chunksize=2, quiet=1, verbose=0))
          >>> with ut.Timer('ut.buffered_generator') as t2:
          ...     result2 = list(ut.buffered_generator(map(func, data)))
          >>> assert len(result1) == num and len(result2) == num and len(result3) == num
          >>> assert result3 == result2, 'inconsistent results'
          >>> assert result1 == result2, 'inconsistent results'
  
      Example1:
          >>> # DISABLE_DOCTEST
          >>> # VERYSLLOOWWW_DOCTEST
          >>> from utool.util_parallel import _test_buffered_generator
          >>> _test_buffered_generator2()
      """
      if buffer_size < 2:
          raise RuntimeError("Minimal buffer_ size is 2!")
  
      if use_multiprocessing:
          print('WARNING seems to freeze if passed in a generator')
          #assert False, 'dont use this buffered multiprocessing'
          if False:
              pool = multiprocessing.Pool(processes=get_default_numprocs(),
                                          initializer=init_worker,
                                          maxtasksperchild=None)
              Process = pool.Process
          else:
              Process = multiprocessing.Process
          _Queue = multiprocessing.Queue
          target = _buffered_generation_process
      else:
          _Queue = queue.Queue
          Process = KillableThread
          target = _buffered_generation_thread
  
      # the effective buffer_ size is one less, because the generation process
      # will generate one extra element and block until there is room in the
      # buffer_.
      buffer_ = _Queue(maxsize=buffer_size - 1)
  
      # previously None was used as a sentinal, which fails when source_gen
      # genrates None need to make object that it will not be generated by the
      # process. A reasonable hack is to use the StopIteration exception instead
      sentinal = StopIteration
  
      process = Process(
          target=target,
          args=(iter(source_gen), buffer_, sentinal)
      )
      #if not use_multiprocessing:
      process.daemon = True
  
      process.start()
  
      while True:
          #output = buffer_.get(timeout=1.0)
          output = buffer_.get()
          if output is sentinal:
              raise StopIteration
          yield output