import multiprocessing as mp

def {{func_name}}_parallel({{func_args|join(', ')}}, batch_size=None):
    if batch_size is None:
        try:
            n = len({{func_args[0]}})
        except Exception:
            n = 0
        batch_size = max(1, n // max(1, mp.cpu_count()*2))

    {% for i in range(stage_count) %}
    def stage{{i+1}}_fn(in_q, out_q, bs):
        while True:
            batch, saw_eof = [], False
            for _ in range(bs):
                x = in_q.get()
                if x is None:
                    saw_eof = True
                    break
                if ({{ stage_exprs[i]['pred'] if stage_exprs[i]['pred'] else 'True' }}):
                    y = ({{ stage_exprs[i]['expr'] }})
                    batch.append(y)
            for y in batch:
                out_q.put(y)
            if saw_eof:
                out_q.put(None)
                break
    {% endfor %}

    queues = [mp.Queue() for _ in range({{stage_count}} + 1)]
    procs = []
    {% for i in range(stage_count) %}
    p{{i+1}} = mp.Process(target=stage{{i+1}}_fn,
                         args=(queues[{{i}}], queues[{{i+1}}], batch_size))
    p{{i+1}}.daemon = True
    p{{i+1}}.start()
    procs.append(p{{i+1}})
    {% endfor %}

    for item in {{func_args[0]}}:
        queues[0].put(item)
    queues[0].put(None)

    results = []
    while True:
        out = queues[-1].get()
        if out is None:
            break
        results.append(out)

    for p in procs: p.join()
    return results
