前情回顾 1. TCP数据传输特点 * 面向连接的传输服务:传输之前必须建立起连接关系 * 可靠的数据传输 * 三次握手和四次挥手能描述 * tcp首部信息 * 字节流方式传输:没有消息边界 * 数据传输过程 短连接与长连接形态 2. tcp套接字编程 服务端 : socket->bind->listen->accept(阻塞1)->recv/send(阻塞2)->close 客户端 : socket--------------->connect->send/recv->close 注意:listen只是表示具备被连接的能力,还要看后面的accept 此外,监听队列大小不代表可以连接多少客户端,表示可以同时的处理几个 客户端发起连接。 3. tcp传输细节 recv它什么时候返回空字节串:突然断开或close断开 send如果对方不存在还发送:管道爆裂报错 tcp套接字缓冲区:可以协调收发速度 粘包问题:发得快收的慢 没有消息边界 4. tcp与udp对比 (可以描述) cookie :了解 在win 下创建进程 (避免子进程再创建子进程...)这样写: if __name__ == "__main__":#顶层模块才运行以下程序 p = Process(target=fun)#功能模块在其他模块运行就不执行以下程序 p.start() 练习01: 文件拆分 假设有一个大文件 ,将这个文件拆分为2个小文件 按照字节平均分即可.要去两个小文件的拆分过程 同时执行,以提高效率. 提示: 获取文件大小 os.path.getsize() plus : 循环读取复制内容 作业 : 1. 能够熟练创建进程 2. 打印100000以内质数,将100000分成4份,创建 4个子进程,每个求其中一份,最后所有子执行完为止. 统计一共用了多少时间
2. 多任务编程
2.1 多任务概述
-
多任务
即操作系统中可以同时运行多个任务。比如我们可以同时挂着qq,听音乐,同时上网浏览网页。这是我们看得到的任务,在系统中还有很多系统任务在执行,现在的操作系统基本都是多任务操作系统,具备运行多任务的能力。
编辑
-
计算机原理
-
CPU:计算机硬件的核心部件,用于对任务进行执行运算。
-
操作系统调用CPU执行任务
-
cpu轮询机制 : cpu都在多个任务之间快速的切换执行,切换速度在微秒级别,其实cpu同时只执行一个任务,但是因为切换太快了,从应用层看好像所有任务同时在执行。
-
多核CPU:现在的计算机一般都是多核CPU,比如四核,八核,我们可以理解为由多个单核CPU的集合。这时候在执行任务时就有了选择,可以将多个任务分配给某一个cpu核心,也可以将多个任务分配给多个cpu核心,操作系统会自动根据任务的复杂程度选择最优的分配方案。
-
并发 : 多个任务如果被分配给了一个cpu内核,那么这多个任务之间就是并发关系,并发关系的多个任务之间并不是真正的"同时"。
-
并行 : 多个任务如果被分配给了不同的cpu内核,那么这多个任务之间执行时就是并行关系,并行关系的多个任务时真正的“同时”执行。
-
-
-
什么是多任务编程
多任务编程即一个程序中编写多个任务,在程序运行时让这多个任务一起(并发或并行:操作系统决定,应用层看来都是并行)运行,而不是一个一个的顺次执行。比如微信视频聊天,这时候在微信运行过程中既用到了视频任务也用到了音频任务,甚至同时还能发消息。这就是典型的多任务。而实际的开发过程中这样的情况比比皆是。
-
实现多任务编程的方法 : 多进程编程,多线程编程
-
-
多任务意义
-
提高了任务之间的配合,可以根据运行情况进行任务创建。
比如: 你也不知道用户在微信使用中是否会进行视频聊天,总不能提前启动起来吧,这是需要根据用户的行为启动新任务。
-
充分利用计算机资源,提高了任务的执行效率。
-
在任务中无阻塞时只有并行状态才能提高效率,阻塞是占用资源的。
-
在任务中有阻塞时并行并发都能提高效率,即利用任务的阻塞时间可以进行其他任务。因为阻塞不占cpu
-
-
2.2 进程(Process)
2.2.1 进程概述
-
定义: 程序在计算机中的一次执行过程。
-
程序是一个可执行的文件,是静态的占有磁盘。
-
进程是一个动态的过程描述,占有计算机运行资源,有一定的生命周期。是动态的。再启动相同程序就开启一个新的进程。
-
-
进程状态
-
三态
就绪态 : 进程具备执行条件,等待系统调度分配cpu资源
运行态 : 进程占有cpu正在运行
等待态 : 进程阻塞等待,此时会让出cpu,不占用cpu资源。
就绪与执行态之间的时间片表示轮循,计算机一启动就有很多程序在阻塞态,例如语音助手等程序,只有当用这个程序的时候才会进入就绪态,然后执行。
-
五态 (在三态基础上增加新建和终止)
新建 : 创建一个进程,获取资源的过程
终止 : 进程结束,释放资源的过程
-
-
进程命令
-
查看进程信息
-
ps -aux
-
-
USER : 进程的创建者
-
PID进程编号 : 操作系统分配给进程的编号,大于0的整数,系统中每个进程的PID都不重复。PID也是重要的识别区分进程的标志。
-
%CPU,%MEM : 占有的CPU和内存大小
-
STAT : 进程状态信息,S,I 表示阻塞状态 ,R 表示就绪状态或者运行状态,快速切换区分不开就绪与运行
-
START : 进程启动时间
-
COMMAND : 通过什么程序启动的进程
-
-
-
-
进程树形结构
pstree
-
父子进程:在Linux操作系统中,进程形成树形关系,任务上一级进程是下一级的父进程,下一级进程是上一级的子进程。linux认为子进程是由父进程创建与发起的。
-
-
2.2.2 多进程编程
-
使用标准库模块 : multiprocessing
-
创建流程
【1】 将需要新进程执行的事件封装为函数,无需返回值,我们也获取不了返回值
【2】 通过模块的Process类创建进程对象,关联函数
【3】 通过进程对象调用start方法启动进程
-
主要类和函数使用
Process() 功能 : 创建进程对象 参数 : target 绑定要执行的目标函数 args 元组,用于给target函数位置传参 kwargs 字典,给target函数键值传参 daemon bool值,让子进程随父进程退出---父进程蹦了,子进程没必要处理了 p.start() 功能 : 启动进程 注意 : 启动进程此时target绑定函数开始执行,该函数作为新进程执行内容,此时进程真正被创建 p.join([timeout]) 功能:阻塞等待子进程退出 参数:最长等待时间,不写就必须等到子进程退出父进程才执行。
新进程会原样拷贝原来进程空间,但不会子子孙孙无穷尽也,linux会自动判断到子进程结束。子进程创建那一刻空间相互独立,互不干扰。
""" 创建进程演示 """ import multiprocessing as mp from time import sleep import os a = 1 print("子进程不执行print")#不同于变量a会存到内存,子进程不会套娃循环执行一次。 # 创建进程执行函数 def fun(): print("创建子进程开始执行了") global a print("a = ", a) a = 1000 print(os.getpid()) sleep(4) print("子进程执行结束了") # 实例化进程对象绑定函数 process = mp.Process(target=fun) # 启动进程:进程产生,运行绑定函数 process.start() print("原有父进程干个事情") # 阻塞等待子进程执行完成 process.join() # 如果不等子进程执行结束,那么父子进程同时执行。 sleep(3) print("a = ", a) print("原有父进程干完了", os.getpid()) # 由于创建进程也需要事件,父进程可能会优先抢占到时间片先执行了 # 偶尔也可能子进程优先抢占,不存在尊老爱幼。
创建的子进程会原样拷贝父进程的内存空间,win与Mac会无线套娃,linux会挑选的自动执行函数部分的进程。这时候父进程依旧往下执行。子进程也往下执行。真实的也是两个进程同时执行,占用同一个打印界面。
-
进程执行现象理解 (难点)
-
新的进程是原有进程的子进程,子进程复制父进程全部内存空间代码段,一个进程可以创建多个子进程。
-
子进程只执行指定的函数,其余内容均是父进程执行内容,但是子进程也拥有其他父进程资源。
-
各个进程在执行上互不影响,也没有先后顺序关系。
-
子进程创建后,各个进程空间独立,相互没有影响。
-
multiprocessing 创建的子进程中无法使用标准输入(即无法使用input,因为父进程已经有了)。
-
a = 1 子进程不执行,与print一样不执行,不一样的是 a 占用内存空间到进程去,print不占内存空间到子进程中去。在子进程修改全局变量,父进程不受到影响,因为在创建子进程时候已经与父进程的内存空间独立出去了。
""" 包含参数的进程函数 """ from multiprocessing import Process from time import sleep # 创建带有参数的进程函数 def worker(sec, name): for i in range(3): sleep(sec) print("I am %s" % name) print("I am working...") # 位置传参,写成元组,必须有序 # process = Process(target=worker, # args=(2, "Tom"))# 形参只有一个也要写成元组形式 # 关键字传参,写成字典,可以无序 # process = Process(target=worker, # kwargs={"name": "Tom", "sec": 2}) # 排在前面参数用位置有序传参,排后面用关键字传参数可无序 process = Process(target=worker, args=(2,), # 必须写成元组,即使有一个元素 kwargs={"name": "Tom"}, daemon=True # 子进程随父进程结束而结束,见打印结果只循环两次子进程就结束 ) process.start() sleep(5.99)
""" 练习01: 文件拆分 假设有一个大文件 ,将这个文件拆分为2个小文件 按照字节平均分即可.要去两个小文件的拆分过程 同时执行,以提高效率. """ # 如果两个进程共用一个打开文件,操作系统就会生成一个偏移量 # 导致读写混乱发生,如果分别打开就不会导致混乱 from multiprocessing import Process import os size = os.path.getsize("bigfile.txt") # 复制上半部分 def readfile1(size): smallfile1 = open("smallfile1.txt", "wb") file = open("bigfile.txt", "rb") while True: data = file.read(1) if file.tell() > (size // 2): break smallfile1.write(data) smallfile1.close() file.close() # 复制下半部分 def readfile2(size): smallfile2 = open("smallfile2.txt", "wb") file = open("bigfile.txt", "rb") file.seek(size // 2) while True: data = file.read(1) if not data: break smallfile2.write(data) smallfile2.close() file.close() process1 = Process(target=readfile1, args=(size,)) process2 = Process(target=readfile2, args=(size,)) process1.start() process2.start() process1.join() process2.join() print("文件拆分结束")
打开一个文件句柄给两个进程同时操作,共用一个文件偏移量,导致偏移量混乱。
2.2.3 进程处理细节
-
进程相关函数
os.getpid() 功能: 获取一个进程的PID值 返回值: 返回当前进程的PID os.getppid() 功能: 获取父进程的PID号 返回值: 返回父进程PID sys.exit(info) 功能:退出进程 参数:字符串 表示退出时打印内容,进程退出提示
""" 创建多个子进程,做多件事情 三个子进程执行同一个函数意义:通过传参让每个子进程达到不同效果 """ from multiprocessing import Process from time import sleep import os, sys def th1(): sleep(1) print("吃饭") print(os.getppid(), "--", os.getpid()) def th2(): sleep(2) sys.exit("不睡觉了") print("睡觉") print(os.getppid(), "--", os.getpid()) def th3(): sleep(4) print("打豆豆") print(os.getppid(), "--", os.getpid()) # 循环创建进程:上面的三个进程有相同的ppid,不同的pid # 所有进程执行完成就永久消失,下次再执行就是新id jobs = [] for th in [th1, th2, th3]: process = Process(target=th) jobs.append(process) # 保存进程对象 process.start() #等待所有子进程结束,父进程才执行print for i in jobs: i.join() print("所有事件执行完毕")
""" 升级:如果子进程功能类似,可使用一个函数完成. 而不是三个函数交给三个子进程完成 """ from multiprocessing import Process from time import sleep import os def th(sec, thing): sleep(sec) print(thing) print(os.getppid(), "--", os.getpid()) jobs = [] for i in [(3, "吃饭"), (2, "睡觉"), (4, "打豆豆")]: process = Process(target=th, args=i) jobs.append(process) process.start() for i in jobs: i.join() print("所有事件执行完毕")
-
孤儿进程和僵尸进程
-
孤儿进程: 父进程先于子进程退出时,子进程会成为孤儿进程,孤儿进程会被系统自动收养,成为孤儿进程新的父进程,并在孤儿进程退出时释放其资源。
-
僵尸进程: 子进程先于父进程退出,并且父进程又没有处理子进程的退出行为状态,此时子进程就会成为僵尸进程。
特点: 僵尸进程虽然结束,但是会存留部分进程资源在内存中,大量的僵尸进程会浪费系统资源。Python模块当中自动建立了僵尸处理机制,每次创建新进程都进行检查,将之前产生的僵尸处理掉,而且父进程退出时候,僵尸也会被系统自动处理。
""" 僵尸进程 """ from multiprocessing import Process import os from time import sleep def fun(): print("我(子进程)的id:",os.getpid(), "我要变成僵尸进程") print("啦啦啦.....") for i in range(5):#建立5个僵尸进程 p = Process(target=fun)#争夺时间片创建id,因此id不是顺序的 p.start() #优化2: 自动检测之前的进程是否有僵尸,sleep后只有一个僵尸了,创建太快就来不及检查 sleep(2) # p.join() #优化1: 如果子进程是僵尸就回收僵尸进程 while True: # 让父进程不结束:父进程结束那么操作系统自动处理子进程僵尸 pass # 查看id状态,发现子进程id变成Z状态即僵尸状态 #终端只有一个僵尸,因为start清除之前的了。
-
2.2.4 创建进程类
进程的基本创建方法将子进程执行的内容封装为函数。如果我们更热衷于面向对象的编程思想,也可以使用类来封装进程内容。
-
创建步骤
【1】 继承Process类
【2】 重写
__init__
方法添加自己的属性,使用super()加载父类属性【3】 重写run()方法
-
使用方法
【1】 实例化对象
【2】 调用start自动执行run方法
""" 自定义进程类:面向对象的编码风格,本质与前面进程一样 """ from multiprocessing import Process from time import sleep # 创建进程和进程执行内容(函数)都写在类中 --> 面向对象 class MyProcess(Process): def __init__(self, value): self.value = value super().__init__() # 你想让进程做什么就写什么函数 def func(self): for i in range(self.value): sleep(2) print('子进程执行了一些内容') # 重要:运行start自动执行run方法,作为进程内容 def run(self): self.func() if __name__ == '__main__': p = MyProcess(3) p.start() # 创建进程 #伪代码:为什么调用start自动执行run # class Process: # def __init__(self,target=None): # self._target = target # # def run(self):#run是接口函数,帮你规定了,里面就是pass,专门给你从写用 # self._target() # # def start(self): # # 创建进程 # self.run()