While the previous answers do work, IMO they can be hard to remember (except of course GNU parallel).
I am somewhat partial to a similar approach to the above (( $i % 10 == 0 )) && wait.  I have also seen this written as ((i=i%N)); ((i++==0)) && wait
where:
N is defined as the number of jobs that you want to run in parallel
and
i is the current job.
While the above approach works, it has diminishing returns as you have to wait for all processes to quit before having a new set of processes work, and this wastes CPU time for any task with any execution time (A.K.A. every task).  In other words, the number of parallel tasks must reach 0 before starting new tasks with the previously described approach.
For me, this issue became apparent when executing a task with an inconsistent execution time (e.g. executing a request to purge user information from a database - the requestee might or might not exist, and if they do exist there could be orders of magnitudes of differences for records associated with different requestees).  What I notices was some requests would be immediately fulfilled, while others would be queued to start waiting for one slightly longer running task to succeed.  This translated to a task that would take hours/days to complete with the previously defined approach only taking tens of minutes.
I think that the below approach is a better solution for maintaining a constant task loading on systems without GNU parallel (e.g. vanilla macOS) and hopefully easier to remember than the above alphabet soup:
WORKER_LIMIT=6 # or whatever - remember to not bog down your system
while read -r LINE; do # this could be any kind of loop
    # there's probably a more elegant approach to getting the number of background processes.
    BACKGROUND_PROCESSES="$(jobs -r | wc -l | grep -Eow '[0-9]+')"
    if [[ $BACKGROUND_PROCESSES -eq $WORKER_LIMIT ]]; then
        # wait for 1 job to finish before starting a new one
        wait -n 
    fi
    # run something in a background shell
    python example.py -item "$LINE" &
done < something.list
# wait for all background jobs to finish
wait