A process pool works best when a job has a clear input and a clear result. Sending a live iterator, a lock or an open connection across that boundary makes ownership harder to reason about. A small serialized payload is easier to test and retry.
You partition work, restore input ordering after out-of-order completion and model independent worker state. No child processes run in this browser. These are checked preparation functions for the boundary you would use in a server application. The final short batch still contains work, completion order doesn't define report order, and separate workers don't share a later append merely because they began with the same values. Make those distinctions explicit in code.
import json
def process_payload(payload: str) -> str:
data = json.loads(payload)
rows, size = data['rows'], data['size']
result = [rows[i:i + size] for i in range(0, len(rows), size)]
return json.dumps(result,separators=(',',':'))
The final partial batch is work too; preserve each row exactly once. Batch starts occur at zero, size, two times size and so on. A short last batch still contains work. All rows are copied once across the output batches; an empty input creates no batch.