def spawn_containers(addrs, env_cls=Environment,
                     env_params=None,
                     mgr_cls=EnvManager, *args, **kwargs):
    """Spawn environments in a multiprocessing :class:`multiprocessing.Pool`.

    Arguments and keyword arguments are passed down to the created environments
    at initialization time if *env_params* is None. If *env_params* is not
    None, then it is assumed to contain individual initialization parameters
    for each environment in *addrs*.

    :param addrs:
        List of (HOST, PORT) addresses for the environments.

    :param env_cls:
        Callable for the environments. Must be a subclass of
        :py:class:`~creamas.core.environment.Environment`.

    :param env_params: Initialization parameters for the environments.
    :type env_params: Iterable of same length as *addrs* or None.

    :param mgr_cls:
        Callable for the managers. Must be a subclass of
        :py:class:`~creamas.mp.EnvManager`.s

    :returns:
        The created process pool and the *ApplyAsync* results for the spawned
        environments.
    """
    pool = multiprocessing.Pool(len(addrs))
    kwargs['env_cls'] = env_cls
    kwargs['mgr_cls'] = mgr_cls
    r = []
    for i, addr in enumerate(addrs):
        if env_params is not None:
            k = env_params[i]
            k['env_cls'] = env_cls
            k['mgr_cls'] = mgr_cls
        # Copy kwargs so that we can apply different address to different
        # containers.
        else:
            k = kwargs.copy()
        k['addr'] = addr
        ret = pool.apply_async(spawn_container,
                               args=args,
                               kwds=k,
                               error_callback=logger.warning)
        r.append(ret)
    return pool, r