def compute_num_processes_and_threads(max_cpu_cores, threads_per_process):
    """Compute the number of allowed processes and threads given the maximum allowed
    number of CPUs and requested number of threads per process.

    Parameters
    ----------
    max_cpu_cores : int or None
        The maximum allowed number of CPU cores to consume by all instances of the process.
        If set the None, it implies no limit to the number of CPU cores that can be used.
    threads_per_process : int
        The user-requested number of threads to use.

    Returns
    -------
    num_processes : int or None
        The computed maximum number of allowed concurrent processes, or None to allow unlimited.
    threads_per_process : int
        The number of threads per process instance which might be less than requested if the
        number of allowed cpus is less than the requested number of threads.

    Examples
    --------
    # max CPU not set
    >>> compute_num_processes_and_threads(None, 8)
    (None, 8)

    # max CPU set, not multiple of threads
    >>> compute_num_processes_and_threads(20, 8)
    (2, 8)

    # max CPU set, multiple of threads
    >>> compute_num_processes_and_threads(24, 8)
    (3, 8)

    # max CPU less than desired threads
    >>> compute_num_processes_and_threads(2, 8)
    (1, 2)
    """
    if max_cpu_cores is None:
        num_processes = None
    elif max_cpu_cores >= threads_per_process:
        num_processes = int(max_cpu_cores / threads_per_process)
    else:
        num_processes = 1
        threads_per_process = max_cpu_cores
    return num_processes, threads_per_process
