03_多进程
1.进程相关的概念
1.同一个程序每次执行都有不同的进程,因为分配的计算机资源不同,进程由代码段,数据段,和PCB(进程控制块)组成
2.进程的特征: 进程是操作系统资源分配的最小单位,每个进程单独占有4G的虚拟内存,进程之间相互独立,运行不受影响
3.程序和进程的区别
进程: 程序在计算机中一次执行的过程,占有CPU内存的计算机资源,有一定的生命周期
程序: 是一个静态的描述,不占有计算机资源
4.进程的状态
1.三态: 就绪态,运行态,等待态
就绪态: 进程具体运行条件,等待系统分配处理器运行
运行态: 进程占有CPU处于运行的状态
等待态: 又称阻塞态,睡眠态,进程暂时不具备运行条件,需要阻塞等待(sleep,accept),此时进程还在内存中等待获取CPU
2.五态: 在三态上增加了新建态和终止态
新建态: 创建一个进程,获取资源,直接表现为运行一个程序或者在程序中创建新的进程
终止态: 进程执行结束,资源回收过程
3.进程特殊的挂起状态: 是指各种原因进程放弃了CPU,导致进程无法继续执行,此时进程被踢出内存
4.进程状态流程图: https://www.processon.com/view/link/5f1059f7f346fb2bfb29f2ba
5.进程优先级
优先级决定了一个进程的纸箱权限和占有资源的优先程度,用户程序默认优先级是0,优先级范围是 -20 ~ 19 # -20最高
nice:
以指定的优先级运行进程
nice -9 ./while.py # 以9的优先级运行此程序,以小于0的优先级运行必须加root权限
sudo nice --9 ./while.py # 用root权限的-9优先级运行此程序
renice
改变某个进程的优先级 # 改变已经在运行中的程序的优先级
sudo renice 2 进程PID号 # 让进程以2的优先级继续运行
6.进程的信息和保存
PCB(进程控制块): 在Unix系统组进程创建后会在内存中开辟一块空间存放进程的相关信息,称为PCB
PID: 在操作系统中进程的唯一标志,是大于0的整数,由系统自动分配
ps -ajx # 可以看到父进程的进程PID
ps -aux # 查看进程信息
用户 PID 占有内存 优先级 进程状态等
D: 等待态(不可中断等待)
S: 等待态(可中断等待)
T: 等待态(暂停)
R: 运行态
Z: 僵尸态
+: 前台进程(不带+即为后台进程)
<: 高优先级
N: 低优先级
l: 有进程链接,即有关联的进程
s: 会话组,有一组进程实现一个大功能,会话组就相当于小组长
top # 动态查看当前运行的进程的状态,q退出,< >翻页
htop # 动态查看当前运行的进程的状态,top命令的升级版
pstree # 以树型结构查看进程
7.进程的调度
1.先来先服务
先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度
FCFS算法比较有利于长作业进程,而不利于短作业进程,由此可知,本算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业进程
2.短作业优先
短作业进程优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度,也可用于进程调度
但其对长作业不利,不能保证紧迫性作业进程被及时处理,作业的长短只是被估算出来的
3.时间片轮转
1.时间片轮转(Round Robin RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例,
在时间片轮转法中,需要将CPU的处理时间分成固定大小的时间片,例如,几十毫秒至几百毫秒,
如果一个进程在被调度选中之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU而排到就绪队列的末尾,
等待下一次调度,同时,进程调度程序又去调度当前就绪队列中的第一个进程
2.因此轮转法只能用来调度分配一些可以抢占的资源,这些可以抢占的资源可以随时被剥夺,而且可以将它们再分配给别的进程,
CPU是可抢占资源的一种,但打印机等资源是不可抢占的,由于作业调度是对除了CPU之外的所有系统硬件资源的分配,
其中包含有不可抢占资源,所以作业调度不使用轮转法
3.在轮转法中,时间片长度的选取非常重要,首先时间片长度的选择会直接影响到系统的开销和响应时间,
如果时间片长度过短,则调度程序抢占处理机的次数增多,这将使进程上下文切换次数也大大增加,从而加重系统开销,
反过来,如果时间片长度选择过长,例如一个时间片能保证就绪队列中所需执行时间最长的进程能执行完毕,则轮转法变成了先来先服务法,
时间片长度的选择是根据系统对响应时间的要求和就绪队列中所允许最大的进程数来确定的
4.在轮转法中,加入到就绪队列的进程有3种情况
一种是分给它的时间片用完,但进程还未完成,回到就绪队列的末尾等待下次调度去继续执行
另一种情况是分给该进程的时间片并未用完,只是因为请求I/O或由于进程的互斥与同步关系而被阻塞,当阻塞解除之后再回到就绪队列
第三种情况就是新创建进程进入就绪队列
如果对这些进程区别对待,给予不同的优先级和时间片从直观上看,可以进一步改善系统服务质量和效率
例如:
我们可把就绪队列按照进程到达就绪队列的类型和进程被阻塞时的阻塞原因分成不同的就绪队列,每个队列按FCFS原则排列,
各队列之间的进程享有不同的优先级,但同一队列内优先级相同,
这样,当一个进程在执行完它的时间片之后,或从睡眠中被唤醒以及被创建之后,将进入不同的就绪队列
4.多级反馈队列
前3种用作进程调度的算法都有一定的局限性,如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,
则短进程优先和基于进程长度的抢占式调度算法都将无法使用,而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,
而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法,在采用多级反馈队列调度算法的系统中,
调度算法的实施过程如下所述
1.应设置多个就绪队列,并为各个队列赋予不同的优先级,第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低,
该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小,
例如,第二个队列的时间片要比第一个队列的时间片长一倍, ...,第i+1个队列的时间片要比第i个队列的时间片长一倍
2.当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度,当轮到该进程执行时,如它能在该时间片内完成,
便可准备撤离系统,如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行,
如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列, ..., 如此下去
当一个长作业进程从第一队列依次降到第n队列后,在第n队列便采取按时间片轮转的方式运行
3.仅当第一队列空闲时,调度程序才调度第二队列中的进程运行,仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行,
如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),
则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程
2.多进程相关的概念
1.多个进程如何占有CPU
一个内核同一时刻只能运行一个任务,多个进程对内核资源进行争夺,最终结果根据操作系统的时间片轮转和进程优先级决定
操作系统决来定哪个进程占用计算机核心,占有计算机核心的进程称为该进程占有CPU的时间片
2.父子进程: 在系统除了初始化进程其它进程都有一个父进程,可能有多个子进程
3.os.fork创建进程的流程
1.用户空间运行程序发起进程创建申请,调用操作系统内核接口创建进程
2.操作系统分配计算机资源,确定进程状态,将新的进程提供给用户使用
4.multiprocessing.Process创建进程的流程
1.需要将进程事件封装成函数,然后使用multiprocessing模块提供的Process类创建进程
2.新的进程和对应的函数相关联,进程启动会自动执行函数完成事件,最后回收进程
5.多进程优缺点
优点: 并行多个任务,提高运行效率,空间独立,数据安全,创建方便
缺点: 进程创建销毁的过程中消耗较多的计算机资源
6.计算机开启的进程数量建议: CPU核数 + 1
3.利用os模块的fork方法实现多进程
1.os模块中进程相关函数实现多进程
1.fork方法概述
父进程中fork之前的内容子进程同样会复制,但父子进程空间各自独立,fork之后的修改不会影响对方
父子进程在执行上互不影响,谁先执行谁后执行完全不确定,子进程虽然复制父进程的空间,但是有自己的特性(如PID,PCB,栈空间等)
fork()函数实现多进程流程图: https://www.processon.com/view/link/5f10dd93f346fb2bfb2a43a0
2.进程的创建
pid = os.fork() # 创建一个新的进程,无参数
创建失败: 返回一个负数(-1)
创建成功: 在子进程中fork的返回值0,在父进程中的返回值是大于0的正整数(新的进程PID)
2.获取PID号
os.getpid() # 获取当前进程的PID号并返回
os.getppid() # 获取当前进程父进程的PID并返回
3.进程的退出
os._exit(status) # 结束一个进程,status表示进程的结束状态,是一个整数
sys.exit([status]) # 结束一个进程并抛出异常,传入一个正整数表示结束状态,传入字符串表示打印结束语
5.fork方法实现多进程
import osimport timeimport sysdef func1(L):time.sleep(
5)L.append(
33)print("this is %s" % sys._getframe().f_code.co_name) # sys._getframe().f_code.co_name 取函数名print("my pid is %s, my list is %s" % (os.getpid(), L))
def func2(L):
time.sleep(3)
L.append(44)
print("this is %s" % sys._getframe().f_code.co_name) # sys._getframe().f_code.co_name 取函数名
print("my pid is %s, my list is %s" % (os.getpid(), L))
def main():
L = [11, 22]
print("准备创建进程")
pid = os.fork()
if pid < 0:
print("创建进程失败")
elif pid == 0:
# os.getpid()取进程pid号 os.getppid()取父进程pid号
print("子进程中执行的代码: pid is %s 父进程的pid是: %s 调用函数func2" % (os.getpid(), os.getppid()))
func2(L)
else:
# 在父进程中pid = os.fork()执行成功后的返回值pid就是子进程的pid号
print("父进程中执行的代码: pid is %s 子进程的pid是: %s 调用函数func1" % (os.getpid(), pid))
func1(L)
print("父子进程都会执行的代码: my pid is %s" % os.getpid())
if__name__ == "__main__":
main()
"""执行结果
准备创建进程
父进程中执行的代码: pid is 17115 子进程的pid是: 17116 调用函数func1
子进程中执行的代码: pid is 17116 父进程的pid是: 17115 调用函数func2
this is func2
my pid is 17116, my list is [11, 22, 44]
父子进程都会执行的代码: my pid is 17116
this is func1
my pid is 17115, my list is [11, 22, 33]
父子进程都会执行的代码: my pid is 17115
"""
2.孤儿进程
1.孤儿进程概述
父进程先于子进程退出,此子进程变成孤儿进程,孤儿进程会被系统指定的进程所收养,即该系统指定的进程成为孤儿进程新的父进程
在孤儿进程退出时,系统指定的进程会进行处理不会使其变成僵尸进程
2.验证孤儿进程的产生
import osimport timedef main():pid
= os.fork()if pid < 0:print("创建新的进程失败")elif pid == 0:time.sleep(
1)# 父进程已经退出,打印的将是系统分配的新的父进程pidprint("父进程的pid是: %s" % os.getppid())
else:
print("父进程的pid是: %s" % os.getpid())
if__name__ == "__main__":
main()
"""执行结果
~/Desktop/python3/demo $ python3 demo5.py
父进程的pid是: 17082
~/Desktop/python3/demo $ 父进程的pid是: 1
"""
3.僵尸进程
1.僵尸进程概述
子进程先于父进程退出,但是父进程没有处理子进程的退出情况,此时子进程变成僵尸进程,僵尸进程会滞留PCB的部分信息在内存中
僵尸进程的危害: 大量的僵尸进程会消耗系统资源应该尽量避免僵尸进程的产生
2.验证僵尸进程的产生
import osdef main():pid
= os.fork()if pid < 0:print("创建新的进程失败")elif pid == 0:print("子进程的pid是: %s" % os.getpid())else:# 子进程退出,父进程不退出print("父进程的pid是: %s" % os.getpid())
while True:
pass
if__name__ == "__main__":
main()
"""执行结果父进程会死循环不结束
~/Desktop/python3/demo $ python3 demo5.py
父进程的pid是: 17199
子进程的pid是: 17200
"""
"""开启一个新的终端根据子进程的pid号验证僵尸进程 Z+
~/Desktop/python3/demo $ ps aux | grep "17200" | grep -v grep
tangxuecheng 17200 0.0 0.0 0 0 s001 Z+ 9:38上午 0:00.00 (Python)
"""
4.避免僵尸进程的产生-让父进程先退出: 这个方法不好控制,实际开发中一般都不会使用
5.避免僵尸进程的产生-让父进程处理子进程的退出(使用wait方法或者waitpid方法)
1.利用wait方法处理子进程-原理是阻塞等待子进程执行结束,所以不推荐使用此方法避免僵尸进程
import osimport timeimport sysdef main():pid
= os.fork()if pid < 0:print("创建新的进程失败")elif pid == 0:print("子进程的pid是: %s" % os.getpid())time.sleep(
1)sys.exit(
1)else:# os.wait()阻塞等待子进程的退出处理子进程后再继续执行父进程,无参数# 返回值: 一个二元元组,第一个值为退出的子进程PID,第二个值为子进程退出状态
p, status = os.wait() # 此时等待子进程执行完毕后解除阻塞
print(p, status, os.WEXITSTATUS(status)) # os.WEXITSTATUS(status)获取结束状态原数
# 子进程退出,父进程不退出
print("父进程的pid是: %s" % os.getpid())
while True:
pass
if__name__ == "__main__":
main()
"""执行结果
子进程的pid是: 17735
17735 256 1
父进程的pid是: 17734
"""
2.利用waitpid方法处理子进程-原理是挂机父进程,当子进程执行结束再执行父进程,所以也不推荐使用
import osimport timeimport sysdef main():pid
= os.fork()if pid < 0:print("创建新的进程失败")elif pid == 0:print("子进程的pid是: %s" % os.getpid())time.sleep(
1)sys.exit(
1)else:# os.waitpid(pid, option)挂起父进程,等待子进程执行结束处理子进程后再执行父进程# 第一个参数pid: -1表示等待任意的子进程退出;>0的整数表示等待相应pid号的子进程退出
# 第二个参数option: 0表示挂起父进程,子进程完成任务后父进程再继续执行父进程;os.WNOHANG表示不挂起父进程
# 返回值: 一个二元元组,第一个值为退出的子进程PID,第二个值为子进程退出状态
p, status = os.waitpid(-1, 0) # 此时挂起父进程等待子进程执行结束后再继续执行父进程,所以不推荐使用此方法
print(p, status, os.WEXITSTATUS(status)) # os.WEXITSTATUS(status)获取结束状态原数
# 子进程退出,父进程不退出
print("父进程的pid是: %s" % os.getpid())
# # 设置为非阻塞状态,循环处理查看子进程状态
# while True:
# try:
# p, status = os.waitpid(-1, os.WNOHANG) # 此时不会挂起父进程而是直接向下继续执行
# print(p, status, os.WEXITSTATUS(status)) # os.WEXITSTATUS(status)获取结束状态原数
# time.sleep(0.5)
# # 子进程退出,父进程不退出
# print("父进程的pid是: %s" % os.getpid())
# except ChildProcessError as e:
# print(e)
# break
while True:
pass
if__name__ == "__main__":
main()
"""执行结果
子进程的pid是: 17899
17899 256 1
父进程的pid是: 17898
"""
6.避免僵尸进程的产生-让父进程处理子进程的退出(使用信号处理)
import osimport signaldef main():pid
= os.fork()if pid < 0:print("创建新的进程失败")elif pid == 0:print("子进程的pid是: %s" % os.getpid())else:# 使用信号避免僵尸进程的原理: 在父进程中忽略子进程发送的信号signal.signal(signal.SIGCHLD, signal.SIG_IGN)
# 子进程退出,父进程不退出
print("父进程的pid是: %s" % os.getpid())
while True:
pass
if__name__ == "__main__":
main()
"""执行结果父进程会死循环不结束
~/Desktop/python3/demo $ python3 demo5.py
父进程的pid是: 3670
子进程的pid是: 3671
"""
"""开启一个新的终端子根据子进程的pid号已近找不到对应的进程
~/Desktop/python3/demo $ ps aux | grep "3671" | grep -v grep
"""
7.避免僵尸进程的产生-创建二级子进程(让二级子进程成为孤儿进程)
1.实现过程
1.父进程创建一级子进程后等待一级子进程退出
2.一级子进程创建二级子进程后马上退出,让二级子进程成为孤儿进程有系统管理
3.让父进程和二级子进程处理具体的事件
2.二级子进程避免僵尸进程示例验证
import osimport sysdef main():# 创建一级子进程pid1 = os.fork()
if pid1 < 0:
print("创建一级子进程失败")
elif pid1 == 0:
# 创建二级子进程
pid2 = os.fork()
if pid2 < 0:
print("创建二级子进程失败")
elif pid2 == 0:
print("二级子进程的pid是: %s" % os.getpid())
# 二级子进程继续处理自己的事件
while True:
pass
else:
# 一级子进程退出,使二级子进程成为孤儿进程
sys.exit(0)
else:
# 等待一级子进程退出
os.wait()
print("父进程的pid是: %s" % os.getpid())
# 父进程继续处理自己的事件
while True:
pass
if__name__ == "__main__":
main()
"""执行结果
二级子进程的pid是: 18629
父进程的pid是: 18627
"""
8.UDP服务端-多任务聊天室
import socketimport sysimport tracebackimport osclass UdpSocketServer:"""UDP聊天室服务器对象"""def__init__(self):
"""完成UDP套接字创建和存储用户信息"""
try:
if len(sys.argv) < 3:
print("python3 udp_server 127.0.0.1 7890")
sys.exit(1)
self.udp_socket_addr = (sys.argv[1], int(sys.argv[2]))
# 1. 创建套接字
self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2. 绑定本地信息
self.udp_socket.bind(self.udp_socket_addr)
except Exception:
traceback.print_exc()
sys.exit(1)
# 用字典存储用户信息{name: (ip, port)}
self.user = dict()
# 客户端发送的消息列表
self.msg_list = None
# 客户端的地址
self.recv_addr = None
# 需要回送给客户端的消息
self.msg = None
def send_msg(self, recv_addr=None):
"""实现单发消息和群发消息"""
# 发送数据
if recv_addr:
self.udp_socket.sendto(self.msg.encode("utf-8"), recv_addr)
else:
self.udp_socket.sendto(self.msg.encode("utf-8"), self.recv_addr)
def recv_msg(self):
"""接收并清洗消息"""
# 1. 接收数据
recv_msg, recv_addr = self.udp_socket.recvfrom(1024)
recv_msg = recv_msg.decode("utf-8")
self.msg_list = recv_msg.split("")
self.recv_addr = recv_addr
def do_login(self):
"""登录"""
if (self.msg_list[1] in self.user) or self.msg_list[1] == "管理员":
self.msg = "该用户已存在,请重新输入"
self.send_msg()
return
self.msg = "OK"
self.send_msg()
# 通知所有人
self.msg = "
欢迎%s进入聊天室" % self.msg_list[1]
for i in self.user:
self.send_msg(self.user[i])
# 将用户插入字典
self.user[self.msg_list[1]] = self.recv_addr
def do_chat(self):
"""群聊"""
self.msg = "
%-4s: %s" % (self.msg_list[1], "".join(self.msg_list[2:]))
# 群发消息给除了客户端自己以外的所有人
for i in self.user:
if i != self.msg_list[1]:
self.send_msg(self.user[i])
def do_quit(self):
"""客户端退出"""
del self.user[self.msg_list[1]]
self.msg = "
" + self.msg_list[1] + "离开了聊天室"
for i in self.user:
self.send_msg(self.user[i])
def do_chlie(self):
"""处理客户端请求"""
# 循环接收请求
while True:
self.recv_msg()
if self.msg_list[0] == "登录":
self.do_login()
elif self.msg_list[0] == "聊天":
self.do_chat()
elif self.msg_list[0] == "退出":
self.do_quit()
else:
self.msg = "错误的请求"
self.send_msg()
def do_parent(self):
"""实现管理员群发消息"""
while True:
msg = input("管理员发言: ")
self.msg = "聊天 %s %s" % ("管理员", msg)
self.send_msg(self.udp_socket_addr)
def run(self):
"""run方法创建父子进程完成功能分类"""
# 创建一级子进程
pid1 = os.fork()
if pid1 < 0:
print("创建一级子进程失败")
elif pid1 == 0:
# 创建二级子进程
pid2 = os.fork()
if pid2 < 0:
print("创建二级子进程失败")
elif pid2 == 0:
# 二级子进程处理客户端请求
self.do_chlie()
else:
# 一级子进程退出,使二级子进程成为孤儿进程,避免僵尸进程的产生
sys.exit(0)
else:
# 等待一级子进程退出
os.wait()
# 父进程发送管理员消息
self.do_parent()
def__del__(self):
"""关闭套接字"""
print("%s对象已回收" % self)
self.udp_socket.close()
def main():
"""程序入口完成主逻辑控制"""
# 实例化一个udp聊天室对象
udp_chat = UdpSocketServer()
# 启动聊天室
udp_chat.run()
if__name__ == "__main__":
main()
9.UDP客户端-多任务聊天室
import socketimport sysimport tracebackimport osimport signalclass UdpSocketClient:"""UDP聊天室客户端对象"""def__init__(self):
"""完成UDP套接字创建和存储用户信息"""
try:
if len(sys.argv) < 3:
print("python3 udp_client 127.0.0.1 7890")
sys.exit(1)
self.udp_socket_addr = (sys.argv[1], int(sys.argv[2]))
# 1. 创建套接字
self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
except Exception:
traceback.print_exc()
sys.exit(1)
# 客户端姓名
self.name = None
# 客户端要发送的消息
self.msg = None
# 记录登录状态
self.login_is_ok = False
def send_msg(self):
"""发送消息"""
self.udp_socket.sendto(self.msg.encode("utf-8"), self.udp_socket_addr)
def recv_login_status(self):
"""接收登录请求结果"""
recv_msg, _ = self.udp_socket.recvfrom(1024) # 用不到返回的地址,因此用_保存
# 判断是否登录成功
if recv_msg.decode("utf-8") == "OK":
print("已经成功进入聊天室")
self.login_is_ok = True
else:
print(recv_msg.decode("utf-8"))
def do_parent(self):
"""整理要发送的请求"""
while True:
self.msg = input("发言(quit退出): ")
if self.msg == "quit":
self.msg = "退出 " + self.name
self.send_msg()
os.kill(os.getppid(), signal.SIGKILL) # 从子进程中杀死父进程
sys.exit("退出聊天室") # 子进程退出
else:
self.msg = "聊天 %s %s" % (self.name, self.msg)
self.send_msg()
def do_chlie(self):
"""接收消息"""
while True:
self.msg, _ = self.udp_socket.recvfrom(1024) # 用不到返回的地址,因此用_保存
print(self.msg.decode("utf-8") + "
发言(quit退出): ", end="")
def run(self):
"""run方法创建父子进程完成功能分类"""
while True:
self.name = input("请输入姓名: ").strip()
self.msg = "登录 " + self.name
self.send_msg()
self.recv_login_status()
if self.login_is_ok:
break
# 创建子进程
pid = os.fork()
if pid < 0:
print("创建子进程失败")
elif pid == 0:
# 子进程发送消息
self.do_parent()
else:
# 父进程接收消息
self.do_chlie()
def__del__(self):
"""关闭套接字"""
print("%s对象已回收" % self)
self.udp_socket.close()
def main():
"""程序入口完成主逻辑控制"""
# 实例化一个udp聊天室对象
udp_chat = UdpSocketClient()
# 启动聊天室
udp_chat.run()
if__name__ == "__main__":
main()
10.简单的并发服务器-fork实现
from socket import *import os
import sys
import signal
def client_handler(c):
try:
print(03_多进程 的全部内容, 来源链接: utcz.com/z/530165.html