#!/bin/sh # shellthreads - pseudo threads in bourne shell # Problem: # All jobs in a shell program are executed like a batch, one after # another. But what if you have jobs that could be executed # concurrently? Imagine a list of downloads, fetched with wget. # Most likely a single wget job wouldn't saturate your bandwidth. # Running more than one wget would utilise your bandwidth more # efficiently. You could start the jobs with ,,wget url &'', but # that would need your constant attention. Could a shell program # do it for you in a clean way? # Answer: # Yes! Even tho it's kind of ugly. Enjoy :). set -u # Exit at undefined variables. set -e # Exit if a command fails. # This could be a list of urls for wget or so. joblist="a b c d e f g h" # Maximum number of worker threads running at the same time. max_threads="3" # Time in seconds the main loop sleeps until it checks # the number of running worker threads again. mainloop_sleep="1" tempdir="/tmp/$(basename $0)_$$" emergency_cleanup() { echo "mainloop $$: emergency exit, killing running threads" for file in $(find "$tempdir" -maxdepth 1 -type f -name "thread-*.pid") ; do pid_to_kill=$(cat $file) echo "mainloop $$: emergency exit, killing thread $pid_to_kill" kill -15 $pid_to_kill && rm $file done #rm -rf "$tempdir" exit 1 } # This is a worker thread. After the job is done, we exit. if [ $# -gt 0 ] ; then # Needed because of ,,set -u''. if [ $1 = thread ] ; then echo "$$" > "$tempdir/thread-$$.pid" echo "thread $$: starting job $2" # Here we do the job, wich is given in $2 from the main loop sleep 8 # The job is done, time to clean up and exit. echo "thread $$, job $2, finished" rm "$tempdir/thread-$$.pid" exit fi fi trap "emergency_cleanup" 2 3 15 mkdir -p $tempdir # This loop will run until we run out of jobs. while [ "$joblist" ] ; do running_threads="$(find "$tempdir" -maxdepth 1 -type f -name "thread-*.pid" | wc -l)" # Start as many worker threads as requested in $max_threads. if [ $running_threads -lt $max_threads ] ; then # Get the current job and remove it from the joblist, then start a new thread. current_job="$(echo $joblist | awk '{print $1}')" joblist="$(echo $joblist | sed 's/'$current_job'//')" echo "mainloop $$: $running_threads running threads, starting new thread with job $current_job" $0 thread $current_job & else echo "mainloop $$: enough threads running already" fi sleep $mainloop_sleep done # All jobs are spawned, now we wait until they finish, then # we clean up. until [ $(find "$tempdir" -maxdepth 1 -type f -name "thread-*.pid" | wc -l) = 0 ] ; do echo "mainloop $$: out of jobs, waiting for worker threads to finish" sleep $mainloop_sleep done rm -r "$tempdir"