多任务并发编程--进程--基于fork--僵尸进程--聊天室

一. 多任务并发编程

  1. 意义: 充分利用计算机资源,同时处理多个任务,提高程序的运行效率

  2. 并行和并发

      并行:多个任务利用计算机多核资源在同时执行,此时多个任务间是并行关系
      
      并发:同时处理多个任务,内核在任务间不断的切换达到很多任务都被同时处理的效果,实际每个时刻只有一个任务在被执行。
    
  3. 实现方法:多进程,多线程

二. 进程 (process)

  1. 定义: 程序在计算机中的一次运行过程

     	* 程序是一个可执行的文件,是静态的只占磁盘
     	* 进程是一个动态的过程,占有计算机运行资源,有一定的生命周期
    
  2. 如何产生一个进程

    【1】 用户空间通过命令或者接口发起请求
    【2】 操作系统接收请求,开始调用系统接口创建进程
    【3】 操作系统调配计算机硬件资源,整合进程状态等进程创建工作
    【4】 操作系统将创建的进程提供给用户使用

  3. 进程概念

    • 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. 孤儿进程:父进程先于子进程退出,此时子进程车称为孤儿进程。
    特点:孤儿进程会被系统进程收养,此时系统进程会成为孤儿进程新的父进程,孤儿进程退出时,系统进程会处理退出行为。

  2. 僵尸进程:子进程先于父进程退出,父进程没有处理子进程退出行为,此时子进程就会成为僵尸进程
    特点:僵尸进程虽然已经结束,但是会存留部分进程信息在内存中,大量的僵尸会浪费系统的内存

  3. 僵尸进程处理
    【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
    

六、群聊服务

  1. 功能:类似QQ群聊功能
    【1】有人进入聊天室需要输入姓名,姓名不能重复
    【2】有人进入聊天室,其他人收到通知。
    xxx进入聊天室
    【3】一个人发消息,其他人会收到消息,
    xxx:xxxxxxxx
    【4】有人退出聊天室,则其他人会收到通知。
    xxx离开了聊天室
    【5】扩展功能:服务器可以向所有群用户发送公报
    管理员消息:xxxxxxxxx
  2. 确定技术模型
    【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()
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值