一. 多任务并发编程
-
意义: 充分利用计算机资源,同时处理多个任务,提高程序的运行效率
-
并行和并发
并行:多个任务利用计算机多核资源在同时执行,此时多个任务间是并行关系 并发:同时处理多个任务,内核在任务间不断的切换达到很多任务都被同时处理的效果,实际每个时刻只有一个任务在被执行。
-
实现方法:多进程,多线程
二. 进程 (process)
-
定义: 程序在计算机中的一次运行过程
* 程序是一个可执行的文件,是静态的只占磁盘 * 进程是一个动态的过程,占有计算机运行资源,有一定的生命周期
-
如何产生一个进程
【1】 用户空间通过命令或者接口发起请求
【2】 操作系统接收请求,开始调用系统接口创建进程
【3】 操作系统调配计算机硬件资源,整合进程状态等进程创建工作
【4】 操作系统将创建的进程提供给用户使用 -
进程概念
-
cpu时间片:如果一个进程占有cpu则称这个进程在cpu时间片上
-
PCB(进程控制块):在内存中开辟的一块空间,用于存放进程信息,也用于操作系统对进程的调配
-
进程ID(PID):系统为每个进程分配的一个大于0的整数,作为进程的ID标志
命令 : ps -aux
-
父子进程:系统中每一个进程(除了初始进程)都有唯一的父进程,可以有0个或者多个子进程
命令 : pstree
ps -ajx -
进程状态
三态:
就绪态: 进程具备执行条件,等待分配cpu资源
运行态: 进程占有cpu时间片正在运行
阻塞态: 进程处理阻塞暂停状态,让出cpu五态 (增加新建和终止):
新建:创建进程获取资源
终止:进程结束释放资源
一、进程的运行特征
1.进程可以使用计算机多核资源
2.进程是计算机资源分配的最小单元
3.进程之间的运行互不影响,各自独立
4.每个进程拥有各自独立的空间,各自使用各自空间内容
-
三. 基于fork的进程创建
- pid = os.fork()
功能:创建新的进程
返回值:整数,如果创建进程失败返回一个负数。如果成功,新进程得到0,原进程得到新进程的PID号
import os
pid = os.fork()
if pid <0:
print("创建进程失败")
elif pid == 0:
print("这是新的进程")
else:
print("这是旧进程")
print("Fork test over")
- 总结:子进程会复制父进程的全部内存空间,从fork的下一句开始执行
父子进程各自运行不影响,顺序不确定
利用父子进程中fork返回值的差异,配合if语句让父子进程执行不同内容几乎是固定搭配
父进程fork之前开辟的空间,子进程也会拥有,父子进程在各自空间操作不会相互影响
父子进程有各自特有的内容,PID,PCB…
四、进程相关函数
1. os.getpid()
功能:获取一个进程的PID值。
返回值:返回当前进程的PID
2. os.getppid()
功能:获取父进程的PID号
返回值:父进程PID
3. os._exit(status)
功能:结束一个进程
参数:表示进程的结束状态 整数
4. sys.exit([status])
功能:结束一个进程
参数:默认为0,整数表示退出状态,字符串表示退出时打印内容
import os
from time import sleep
pid = os.fork()
if pid < 0 :
print("ERROR")
elif pid==0:
sleep(1)
print("Child PID",os.getpid())
print("Get parent pid",os.getppid())
else:
print("Parent PID",os.getpid())
五、孤儿进程
-
孤儿进程:父进程先于子进程退出,此时子进程车称为孤儿进程。
特点:孤儿进程会被系统进程收养,此时系统进程会成为孤儿进程新的父进程,孤儿进程退出时,系统进程会处理退出行为。 -
僵尸进程:子进程先于父进程退出,父进程没有处理子进程退出行为,此时子进程就会成为僵尸进程
特点:僵尸进程虽然已经结束,但是会存留部分进程信息在内存中,大量的僵尸会浪费系统的内存 -
僵尸进程处理
【1】使用wait()函数处理子进程退出pid,status = os.wait() 功能:阻塞等待子进程退出 返回值:退出的子进程pid, 子进程的退出状态
【2】pid,status = os.waitpid(pid,option)
功能:阻塞等待子进程退出 参数:pid -1 等待任意子进程退出 >0 表示等待指定子进程 option 0 表示阻塞 os.WNOHANG 表示非阻塞 返回值:退出的子进程pid, 子进程的退出状态
import os from time import sleep pid = os.fork() if pid < 0: print("Error") elif pid == 0: sleep(3) print("Child %d process exit"%os.getpid()) os._exit(2) else: # pid,status = os.wait() # 非阻塞模式 pid,status = os.waitpid(-1,os.WNOHANG) print("pid:",pid) # 获取子进程退出 print("status:",os.WEXITSTATUS(status)) while True: sleep(100)
【3】创建二级子进程
1.父进程创建子进程wait等待回收 2.子进程创建二级子进程后退出 3.二级子进程称为孤儿,和父进程一起完成事件。
# 二级子进程处理僵尸 import os from time import sleep import signal def f1(): sleep(3) print("旭哥爱保健") def f2(): sleep(4) print("波波有故事") pid = os.fork() if pid < 0: print("ERROR") elif pid == 0: p = os.fork()#创建二级子进程 if p == 0: f1() else: os._exit(0)#一级子进程退出 else: os.wait() #回收一级子进程 f2()
【4】信号处理
原理:子进程退出会发送信号给父进程,如果父进程忽略子进程信号,则系统会自动处理子进程退出 方法: import signal signal.signal(signal.SIGCHLD,signal.SIG_IGN) 特点:非阻塞、使用该方法,可以处理所有子进程退出
import signal import os # 处理子进程信号 signal.signal(signal.SIGCHLD,signal.SIG_IGN) pid = os.fork() if pid <0: print("ERROR") elif pid == 0: print("Child pid:",os.getpid()) else: while True: pass
六、群聊服务
- 功能:类似QQ群聊功能
【1】有人进入聊天室需要输入姓名,姓名不能重复
【2】有人进入聊天室,其他人收到通知。
xxx进入聊天室
【3】一个人发消息,其他人会收到消息,
xxx:xxxxxxxx
【4】有人退出聊天室,则其他人会收到通知。
xxx离开了聊天室
【5】扩展功能:服务器可以向所有群用户发送公报
管理员消息:xxxxxxxxx - 确定技术模型
【1】消息的网络传输:socket–>udp
【2】发送模型:
转发–>服务端–>其他客户端
【3】服务端用户存储{name:addr}
【4】收发关系处理:多线程分别处理消息接收和发送
3.整体设计
【1】封装方法:函数
4.注意点:
【1】写一个模块测试一个模块
【2】注释的添加
slient
from socket import *
import os, sys
# 服务器地址
ADDR = ("127.0.0.1", 9999)
#
def udp_client():
return socket(AF_INET, SOCK_DGRAM)
def login(s):
while True:
name = input("请输入姓名:")
msg = "L " + name # L表示请求类型
# 给服务器发送
s.sendto(msg.encode(), ADDR)
# 等待回复
data, addr = s.recvfrom(1024)
if data.decode() == "OK":
print("您已进入聊天室")
break
else:
print(data.decode())
return name
def send_msg(s, name):
while True:
try:
text = input("发言:")
except KeyboardInterrupt:
text = "quit"
if text.strip() == "quit":
msg = "Q " + name
s.sendto(msg.encode(), ADDR)
sys.exit("退出聊天室")
msg = "C %s %s" % (name, text)
s.sendto(msg.encode(), ADDR)
def recv_msg(s):
while True:
data, addr = s.recvfrom(2048)
if data.decode() == "EXIT":
sys.exit()
print(data.decode()+"\n发言",end = "")
def chat(s, name):
pid = os.fork()
if pid < 0:
sys.exit("ERROR")
elif pid == 0:
send_msg(s, name)
else:
recv_msg(s)
if __name__ == "__main__":
s = udp_client()
# s.sendto(b"Test",ADDR)
name = login(s)
chat(s, name)
server
# -*-coding:utf-8 -*-
'''
Chat room server
env:python3.5
exc:for socket and fork
'''
from socket import *
import os,sys
# 服务端地址
ADDR = ("0.0.0.0",9999)
# 存储用户{name,addr}
user = {}
# 搭建网络连接
def udp_server():
# 创建套接字
s = socket(AF_INET,SOCK_DGRAM)
s.bind(ADDR)
return s
def do_login(s,name,addr):
if name in user or ("管理员" in name):
s.sendto("该用户已存在".encode(),addr)
return
s.sendto(b"OK",addr)
# 通知其他人
msg = "\n您的好友 %s 已上线"%name
for i in user:
s.sendto(msg.encode(),user[i])
user[name] = addr
def do_chat(s,name,text):
msg = "\n%s :%s"%(name,text)
for i in user:
if i != name:
s.sendto(msg.encode(),user[i])
def do_quit(s,name):
msg = "\n%s 退出了聊天室"%name
for i in user:
if i != name:
s.sendto(msg.encode(),user[i])
else:
s.sendto(b"EXIT",user[i])
#删除用户
del user[name]
def request(s):
while True:
data,addr = s.recvfrom(1024)
msgList = data.decode().split(" ")
#区分请求类型
if msgList[0] == "L":
do_login(s,msgList[1],addr)
elif msgList[0] == "C":
# 重组消息
text = " ".join(msgList[2:])
do_chat(s,msgList[1],text)
elif msgList[0]=="Q":
do_quit(s,msgList[1])
def main():
s = udp_server()
pid = os.fork()
if pid < 0:
print("ERROR")
elif pid == 0:
while True:
msg = input("管理员消息:")
msg = "C 管理员消息 "+msg
s.sendto(msg.encode(),ADDR)
else:
request(s)
main()