Skip to content

Instantly share code, notes, and snippets.

@programminghoch10
Last active September 7, 2025 11:52
Show Gist options
  • Select an option

  • Save programminghoch10/7b240002e3ac645fdb01478619e7bf5c to your computer and use it in GitHub Desktop.

Select an option

Save programminghoch10/7b240002e3ac645fdb01478619e7bf5c to your computer and use it in GitHub Desktop.
Simple bash script parallelization using semaphores
#!/bin/bash
SEMPATH="/tmp"
SEMNAME=""
semtake() {
local name="$1"
[ -z "$name" ] && echo "Missing semaphore name!" && return 1
local j="$2"
[ -z "$2" ] && j=$(nproc)
[ -n "$SEMNAME" ] && echo "Already have $SEMNAME" && return 1
while true; do
for i in $(seq 1 $j); do
SEMNAME=".semlock-$name-$j-$i"
mkdir "$SEMPATH/$SEMNAME" 2>/dev/null && break 2
done
sleep 1
done
trap semgive EXIT
}
semgive() {
[ -z "$SEMNAME" ] && return
rmdir "$SEMPATH/$SEMNAME" &>/dev/null || true
SEMNAME=""
}
#!/bin/bash
[ -z "$(command -v inotifywait)" ] && echo "inotify-tools need to be installed for $0 to work!" >&2 && return 1
SEMPATH="/tmp"
[ ! -d "$SEMPATH" ] && echo "$SEMPATH is not a valid directory" >&2 && return 1
! (return 0 2>/dev/null) && echo "$0 can only be sourced, not executed" >&2 && exit 1
#SEMNAME=""
#SEMNAMEID=""
semtake_pool() {
local SEMNAME="$1"
local j="$2"
for i in $(seq 1 "$j"); do
SEMNAMEID="$i"
mkdir "$SEMPATH/$SEMNAME-$SEMNAMEID" 2>/dev/null || continue
return 0
done
unset SEMNAMEID
return 1
}
semtake() {
local name="$1"
[ -z "$name" ] && echo "Missing semaphore name!" >&2 && return 1
local j="$2"
[ -z "$2" ] && j=$(nproc)
[ -n "$SEMNAMEID" ] && echo "Already have $SEMNAME" >&2 && return 1
SEMNAME=".semlock-$name"
until semtake_pool "$SEMNAME" "$j"; do
local i
i="$(find "$SEMPATH" -maxdepth 1 -type d -name "$SEMNAME-wait-*" 2>/dev/null | sed 's/^.*-\([[:digit:]]*\)$/\1/' | sort -n | tail -1)"
[ -z "$i" ] && i=0
local SEMWAITNAME
while true; do
SEMWAITNAME="$SEMNAME"-wait-$i
i=$((i+1))
mkdir "$SEMPATH"/"$SEMWAITNAME" &>/dev/null || continue
break
done
inotifywait --quiet --quiet --event delete_self "$SEMPATH"/"$SEMWAITNAME"
rmdir "$SEMPATH"/"$SEMWAITNAME" &>/dev/null || true
done
trap semgive EXIT
}
semgive() {
[ -z "$SEMNAME" ] && return
[ -z "$SEMNAMEID" ] && return
rmdir "$SEMPATH"/"$SEMNAME"-"$SEMNAMEID" &>/dev/null || true
unset SEMNAMEID
local i
i="$(find "$SEMPATH" -maxdepth 1 -type d -name "$SEMNAME-wait-*" 2>/dev/null | sed 's/^.*-\([[:digit:]]*\)$/\1/' | sort -n | head -1)"
[ -z "$i" ] && return
local SEMWAITNAME
local waiter
for waiter in "$SEMPATH"/"$SEMNAME"-wait-*; do
SEMWAITNAME="$SEMNAME"-wait-$i
i=$((i+1))
rmdir "$SEMPATH"/"$SEMWAITNAME" &>/dev/null || continue
break
done
unset SEMNAME
}
@programminghoch10
Copy link
Author

programminghoch10 commented Nov 1, 2022

semnotify.sh

Another implementation of semlock.sh.
It features the exact same usage as semlock.sh, so the instructions and documentation from semlock.sh apply.

This variant uses inotify-tools to notify the next waiting process that the semaphore is available.
This way we achive two additional points:

  1. No busy waiting required, as the processes are passively waiting on filesystem changes.
  2. Ordered execution, because the waiting line is now numbered and semaphores will be distributed "first come, first serve"

This can be used as a drop-in replacement to semlock.sh.
If you have inotify-tools installed, simply download semnotify.sh, rename it to semlock.sh and replace the other implementation.

@programminghoch10
Copy link
Author

Reserved

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment