Linux shell实现多进程进程池参考文档 https://zhuanlan.zhihu.com/p/546235448 https://seekstar.github.io/2023/09/25/linux-shell向named-pipe写入时不自动eof/ 备注这里只是用下载举一个耗时任务的例子真正的下载还受到带宽的影响带宽慢一个进程就吃满了进程多也未必管用假设要从一个给定的 img.txt 文本下载一批文件文本中每一行都是一个文件下载链接样式如下http://www.test.com/download/a.zip http://www.test.com/download/b.zip http://www.test.com/download/c.zip...1、串行执行for循环单线程串行执行效率低同一时间只能执行一个任务。#!/bin/bashforurlin$(catimg.txt);doechodownload:${url}# wget ${url}sleep1sdoneechoyou have download all files2、并行执行不控制进程数量将一堆语句用括起来在末尾加一个 shell就会启动一个子进程去执行里面的内容使用 wait 命令可以阻塞当前线程等待这些子进程全部执行完之后再执行剩下的语句。每循环一次都会开启一个子进程如果下载链接特别多短时间内会创建大量的子进程每个进程都需要分配内存会消耗服务器大量资源。进程太多CPU大量时间都花在进程调度上面真正用于执行任务的时间片占比很少。任务少能用任务多不推荐。#!/bin/bashforurlin$(catimg.txt);do# 这里的 会开启一个子进程执行{echodownload:${url}# wget ${url}sleep1s}done# 使用 wait 命令阻塞当前进程直到所有子进程全部执行完waitechoyou have download all files3、使用命名管道控制子进程数量mkfifo 命令创建命名管道命名管道可以实现多个进程之间的通信特点管道缓冲区一般是64KB没满时进程可正常写入缓冲区满了进程写入会被阻塞缓冲区非空进程可正常读取缓冲区空了进程再读取会被阻塞阻塞队列。如果关闭了一个管道的所有写fd则操作系统会给管道的所有reader发送一个EOF信号可以使用 exec 命令给管道分配一个写fd保证至少存在一个写fd防止系统给reader发送EOF信号。分配fd时不要使用0 1 2 255这4个fd已经被操作系统使用分别对应stdin、stdout、stderr、系统保留fd可使用命令 ls -lh /proc/$$/fd 查看进程已分配的fd。使用多个read命令读取同一个管道时可能会发生字符截断错位的情况。read命令按字符读取一直读取到换行符为止一行字符可能会被几个read命令获取到比如abcdefg是一行a进程read命令获取到ace三个字符b进程read命令获取到bd两个字符c进程获取到fg两个字符导致每个read命令都不能获取到完整的一行字符使用多个read命令时要加锁。利用管道阻塞队列的特性来控制进程数量往管道中放入固定个数的令牌主进程每从管道中取走一个令牌就可以开启一个子进程令牌取完后主进程会被阻塞。子进程结束前再将令牌归还到管道管道中有了令牌后主进程又可以取走令牌开启子进程这样就可以控制开启的子进程的数量了子进程数量和CPU核心数量相等时效率最高。#!/bin/bash# 创建一个管道mkfifomylist# 给管道分配读写fd写fd防止操作系统发送EOF如果只分配写fd则必须在读管道后执行否则阻塞exec4mylist# 管道中放入4个令牌回车符foriin{1..4};doechomylistdoneforurlin$(catimg.txt);do# 创建子进程前先从管道中取走一个令牌回车符当循环4次后管道空了主进程就会被阻塞# 一直等到管道中又有令牌回车符了主进程才被唤醒继续执行readmylist# 这里的 会开启一个子进程执行{echodownload:${url}# wget ${url}sleep1s# 子进程结束前归还令牌回车符echomylist}done# 使用 wait 命令阻塞当前进程直到所有子进程全部执行完waitechoyou have download all files# 全部结束后删除fd和管道exec4-exec4-rm-fmylist4、参考进程池实现多进程直接使用管道传递任务参数开启固定个数的子进程每个子进程内部使用 read 命令不断从管道中读取数据直到管道为空读取不到数据了再关闭子进程。相比上面的分配令牌的实现方式省去创建子进程的开销性能更高。注意多个read命令读取同一个管道会字符错乱需要加锁。其他队列都可以按照此方式实现多进程redisrabbitmqkafka之类的都可以另外要注意必须先开启子进程然后再将数据写入到管道。防止写满管道缓冲区导致主进程被一直阻塞先开启子进程再向管道文件中写入数据数据一写入管道缓冲区马上就被子进程取走不会出现主进程被阻塞的问题。#!/bin/bash# 创建一个管道传递任务参数mkfifomylist# 给管道分配fdexec4mylist# 再创建一个管道锁文件用于多个read命令加解锁mkfifomylock# 给管道分配fdexec5mylock# 事先向锁文件中放入1条数据解锁echomylock# 开启4个子进程foriin{1..4};do# 这里的 会开启一个子进程执行{# 先读取锁文件加锁由于锁文件中只有1条数据读取完之后锁文件空了其他子进程再读取时只能等待# 不要使用 read -t无法保证原子操作whilereadmylockreaddatamylist;do# 读取到业务数据后立即写入1条数据到锁文件解锁让其他子进程继续读取数据echomylock# 判断是否是进程结束标志如果是则结束进程if[[PROCESS_END${data}]];thenbreakfi# 子进程会复制父进程的数据可以使用分配的fd来操作管道# 正常应该通过 exec 4- 关闭写fd系统发送EOF来结束read命令# 这里用不了这种方式read命令阻塞执行不到 exec 4-# 只能通过上面这种进程内部判断的方式结束进程echodownload:${data}# wget ${data}sleep1sdone}done# 将img.txt中的链接全部插入到管道中forurlin$(catimg.txt);doecho${url}mylistdone# 插入完业务数据后再插入对应进程数量的进程结束标志注意区别业务数据foriin{1..4};doechoPROCESS_ENDmylistdone# 使用 wait 命令阻塞当前进程直到所有子进程全部执行完waitechoyou have download all files# 全部结束后解绑文件描述符并删除管道exec4-exec4-rm-fmylistexec5-exec5-rm-fmylock