1#!/usr/bin/env bash
2set -euo pipefail
3
4# Parallel Execution Patterns
5# Demonstrates running multiple tasks concurrently with controlled parallelism
6
7# ============================================================================
8# Color definitions for output
9# ============================================================================
10
11readonly RED='\033[0;31m'
12readonly GREEN='\033[0;32m'
13readonly YELLOW='\033[1;33m'
14readonly BLUE='\033[0;34m'
15readonly CYAN='\033[0;36m'
16readonly NC='\033[0m' # No Color
17
18# ============================================================================
19# Parallel Execution Functions
20# ============================================================================
21
22# Run commands in parallel with concurrency limit
23# Usage: run_parallel <max_jobs> <command1> <command2> ...
24run_parallel() {
25 local max_jobs="$1"
26 shift
27 local -a commands=("$@")
28
29 echo -e "${CYAN}Running ${#commands[@]} commands with max $max_jobs parallel jobs${NC}"
30
31 local -a pids=()
32 local job_count=0
33
34 for cmd in "${commands[@]}"; do
35 # Wait if we've reached the concurrency limit
36 while [[ ${#pids[@]} -ge $max_jobs ]]; do
37 wait_for_any_pid pids
38 done
39
40 # Launch command in background
41 (
42 eval "$cmd"
43 ) &
44
45 local pid=$!
46 pids+=("$pid")
47 ((job_count++))
48
49 echo -e " ${GREEN}→${NC} Started job $job_count (PID $pid): $cmd"
50 done
51
52 # Wait for all remaining jobs
53 echo -e "${CYAN}Waiting for remaining jobs to complete...${NC}"
54 for pid in "${pids[@]}"; do
55 wait "$pid" 2>/dev/null || true
56 done
57
58 echo -e "${GREEN}✓${NC} All jobs completed"
59}
60
61# Wait for any PID in the array to finish, then remove it
62wait_for_any_pid() {
63 local -n pid_array=$1
64
65 # Wait for any child process
66 wait -n 2>/dev/null || true
67
68 # Remove finished PIDs from array
69 local -a still_running=()
70 for pid in "${pid_array[@]}"; do
71 if kill -0 "$pid" 2>/dev/null; then
72 still_running+=("$pid")
73 fi
74 done
75
76 pid_array=("${still_running[@]}")
77}
78
79# Wait for specific PIDs and collect their exit codes
80# Returns: associative array of PID -> exit code
81wait_for_pids() {
82 local -a pids=("$@")
83
84 echo -e "${CYAN}Waiting for ${#pids[@]} process(es)...${NC}"
85
86 local -A exit_codes=()
87
88 for pid in "${pids[@]}"; do
89 local exit_code=0
90 wait "$pid" || exit_code=$?
91
92 exit_codes[$pid]=$exit_code
93
94 if [[ $exit_code -eq 0 ]]; then
95 echo -e " ${GREEN}✓${NC} PID $pid completed successfully"
96 else
97 echo -e " ${RED}✗${NC} PID $pid failed with exit code $exit_code"
98 fi
99 done
100
101 # Print summary
102 local failed_count=0
103 for exit_code in "${exit_codes[@]}"; do
104 [[ $exit_code -ne 0 ]] && ((failed_count++))
105 done
106
107 if [[ $failed_count -eq 0 ]]; then
108 echo -e "${GREEN}✓${NC} All processes completed successfully"
109 else
110 echo -e "${YELLOW}⚠${NC} $failed_count process(es) failed"
111 fi
112
113 return $failed_count
114}
115
116# Simulate a download task
117simulate_download() {
118 local file_id="$1"
119 local duration="$2"
120
121 echo -e " ${BLUE}[File $file_id]${NC} Starting download..."
122 sleep "$duration"
123
124 # Simulate occasional failures
125 if [[ $((RANDOM % 10)) -eq 0 ]]; then
126 echo -e " ${RED}[File $file_id]${NC} Download failed!"
127 return 1
128 fi
129
130 echo -e " ${GREEN}[File $file_id]${NC} Download complete (${duration}s)"
131 return 0
132}
133
134# Parallel download demonstration
135parallel_download() {
136 local max_parallel="$1"
137 local -a files=("${@:2}")
138
139 echo -e "${CYAN}Downloading ${#files[@]} files with max $max_parallel parallel downloads${NC}\n"
140
141 local -a pids=()
142 local start_time=$SECONDS
143
144 for file_id in "${files[@]}"; do
145 # Limit concurrency
146 while [[ ${#pids[@]} -ge $max_parallel ]]; do
147 wait_for_any_pid pids
148 done
149
150 # Random duration between 1-3 seconds
151 local duration=$((1 + RANDOM % 3))
152
153 simulate_download "$file_id" "$duration" &
154 pids+=($!)
155 done
156
157 # Wait for remaining downloads
158 for pid in "${pids[@]}"; do
159 wait "$pid" 2>/dev/null || true
160 done
161
162 local elapsed=$((SECONDS - start_time))
163 echo -e "\n${GREEN}✓${NC} All downloads completed in ${elapsed}s"
164}
165
166# ============================================================================
167# Demo Section
168# ============================================================================
169
170demo_sequential_vs_parallel() {
171 echo -e "\n${BLUE}=== Sequential vs Parallel Execution ===${NC}\n"
172
173 # Define test tasks
174 local -a tasks=(
175 "sleep 1 && echo 'Task 1 done'"
176 "sleep 1 && echo 'Task 2 done'"
177 "sleep 1 && echo 'Task 3 done'"
178 "sleep 1 && echo 'Task 4 done'"
179 )
180
181 # Sequential execution
182 echo -e "${YELLOW}Sequential execution:${NC}"
183 local start=$SECONDS
184 for task in "${tasks[@]}"; do
185 eval "$task"
186 done
187 local seq_time=$((SECONDS - start))
188 echo -e "Time: ${seq_time}s\n"
189
190 # Parallel execution
191 echo -e "${YELLOW}Parallel execution (max 4 jobs):${NC}"
192 start=$SECONDS
193 run_parallel 4 "${tasks[@]}"
194 local par_time=$((SECONDS - start))
195 echo -e "Time: ${par_time}s\n"
196
197 echo -e "${GREEN}Speedup:${NC} ~$((seq_time / par_time))x faster"
198}
199
200demo_parallel_with_limit() {
201 echo -e "\n${BLUE}=== Parallel Execution with Concurrency Limit ===${NC}\n"
202
203 local -a tasks=()
204 for i in {1..8}; do
205 tasks+=("sleep 0.5 && echo 'Task $i completed'")
206 done
207
208 echo -e "${YELLOW}Running 8 tasks with max 3 parallel jobs:${NC}"
209 run_parallel 3 "${tasks[@]}"
210}
211
212demo_pid_tracking() {
213 echo -e "\n${BLUE}=== PID Tracking and Exit Code Collection ===${NC}\n"
214
215 # Start several background processes
216 sleep 0.5 && exit 0 &
217 local pid1=$!
218
219 sleep 0.5 && exit 1 &
220 local pid2=$!
221
222 sleep 0.5 && exit 0 &
223 local pid3=$!
224
225 sleep 0.5 && exit 2 &
226 local pid4=$!
227
228 echo "Started 4 background processes: $pid1, $pid2, $pid3, $pid4"
229 echo
230
231 wait_for_pids "$pid1" "$pid2" "$pid3" "$pid4"
232}
233
234demo_parallel_downloads() {
235 echo -e "\n${BLUE}=== Parallel Downloads Simulation ===${NC}\n"
236
237 # Test with different concurrency levels
238 local -a files=(1 2 3 4 5 6 7 8)
239
240 echo -e "${YELLOW}Test 1: Sequential (1 at a time)${NC}"
241 parallel_download 1 "${files[@]}"
242
243 echo -e "\n${YELLOW}Test 2: Parallel (4 at a time)${NC}"
244 parallel_download 4 "${files[@]}"
245}
246
247demo_job_control() {
248 echo -e "\n${BLUE}=== Job Control Example ===${NC}\n"
249
250 echo "Starting 5 background jobs..."
251
252 local -a pids=()
253 for i in {1..5}; do
254 (
255 sleep $((1 + RANDOM % 3))
256 echo "Job $i finished"
257 ) &
258 pids+=($!)
259 echo -e " Started job $i (PID ${pids[-1]})"
260 done
261
262 echo
263 echo "Active background jobs:"
264 jobs -l
265
266 echo
267 echo "Waiting for all jobs to complete..."
268 for pid in "${pids[@]}"; do
269 wait "$pid"
270 done
271
272 echo -e "${GREEN}✓${NC} All jobs completed"
273}
274
275# ============================================================================
276# Main Execution
277# ============================================================================
278
279main() {
280 echo -e "${BLUE}╔════════════════════════════════════════════╗${NC}"
281 echo -e "${BLUE}║ Parallel Execution Demo ║${NC}"
282 echo -e "${BLUE}╚════════════════════════════════════════════╝${NC}"
283
284 demo_sequential_vs_parallel
285 demo_parallel_with_limit
286 demo_pid_tracking
287 demo_parallel_downloads
288 demo_job_control
289
290 echo -e "\n${GREEN}=== All Parallel Execution Demos Complete ===${NC}\n"
291}
292
293main "$@"