python学习第七周总结
阅读原文时间:2023年07月08日阅读:1
1C/S模块:
    1.1Client:客户端:客户端也就是你这一端可以接收到的程序,手机app,web网页等,我们可以在客户端页面上向度武器发送请求以及数据,交给服务器处理。
    1.2Service:服务端:也就是提供'服务',接收并储存客户端发过来的数据或请求,并且对请求进行响应,发送数据给客户端。
"""
我们使用计算机下载下来一个app本质是各大互联网公司的客户端软件,通过这些客户端软件我们就可以体验到各个互联网公司给我们提供的服务
eg:淘宝客户端体验购物服务
PS:一般情况下客户端与服务端是需要通过互联网来交互的,但是有的情况下不需要,因为客户端和服务端在一台计算机上
"""
    1.3 作为客户端必备的条件:
        1.24小时提供服务
        2.固定的地址
        3.能同事服务多个客人

2.B/S架构:浏览器充当所有服务端的客户端
    Browser:浏览器
    Service:服务端/器
"""
B/S架构的本质还是C/S架构
"""
3.B/S架构和C/S架构的区别对比:
C/S架构:
    优势:不同的客户端由不同的公司独立开发,可以高度定制化客户端功能    劣势:需要下载才能使用
B/S架构:
    优势:使用方便不需要下载即可使用
    劣势:开发必须遵守浏览器的规则,无法做到高度定制化


ATM:三层架构
选课系统:三层架构
本质也属于软件开发架构的范畴

软件设计的大方向>>>:统一接口
    微信小程序
    支付宝小程序


1.什么是网络编程?
    基于网络写代码,能够实现数据远程交互
2.学习网络编程的目的:能够开发CS架构的软件
3.网络编程的起源:
"""
    最早起源于美国军事领域
        想实现计算机之间数据的交互
            最早的时候只能用硬盘拷贝
            之后发明了网络编程
    """
4.网络编程必备条件
    数据的远程交互
         1.早期的电话
            电话线
          2.早期的大屁股电脑
            网线
         3.笔记本电脑、移动电话
            网卡
    ps:实现数据的远程交互必备的基础条件是物理连接介质


1.OSI七层协议:规定了所有计算机在远程数据交互的时候必须经过相同的处理流程、在制造过程中必须有相同的功能硬件

2.七层协议包含:应用层、表示层、会话层、传输层、网络层、数据链路层、物理连接层

3.浓缩为四层:应用层、传输层、网络层、网络接口层


主要用于确保计算机之间物联连接介质,接受数据(bytes类型、二进制)


1.规定了电信号的分组方式
2.以太网协议:
    2.1 规定了计算机在出厂时候必须有一块网卡、网卡上有一串数字
    2.2 该数字相当于计算机的身份证号,是独一无二的
    2.3 该数字的特征:12位16进制的数据,前六位为厂商编号,前6位为流水线号
    2.4 该数字也称为:以太网地址或MAC地址


1.交换机:能够将接入的计算机彼此联系起来

2.广播:首次查找接入同一个交换机的计算机,需要向此交换机内所有的计算机'广播'

3.单播:首次被查找的计算机回应查找它的计算机,并且附上自己的MAC地址

4.广播风暴:接入同一台交换机的多台计算机同时发出广播,导致网络性能下降,甚至网络瘫痪

5.局域网:可以简单地理解为有单个交换机的网络,在局域网可以直接使用mac地址通信。局域网是封闭型的,可以由办公室的两台计算机组成,也可以有一个公司内的上千台计算机组成

6.广域网:可以简单得理解为更大范围的局域网

7.互联网:有所有的局域网,广域网连接在一起的网络

8.路由器:不同局域网计算机之间无法实现数据交互,需要路由器连接,因此,路由器具有判断网络地址和选择IP路径的功能



IP协议:规定了所有接入互联网的计算机都有一个IP地址,类似于身份证号
"""
MAC地址和IP地址的区别:
MAC地址是固定的,才可以看成永远无法改变
IP地址是动态的,不同场所IP地址是不同的
"""
IP地址特征: IPV4用点分十进制来表示。
"""
点分十进制表示,是IPV4的IP地址标识方法,IPV4中用4个字节表示一个IP地址,每个字节按照十进制表示0-255,点分十进制就是用4组从0-255的数字,来表示一个IP地址。
"""
IPV6:能够给地球上每一粒沙分一个IP地址(2**128个地址)
IP地址可以跨局域网传输
ps:IP地址可以用来标识全世界独一无二的一台计算机
"""
ARP协议:能够根据IPO地址动态解析出MAC地址
"""


PORT协议:
    用来标识一台计算机上面某一个应用程序,范围:0-65535
    特征:动态分配
    建议:
        0-1024:系统默认需要使用
        1025-8000:常见的软件端口号
URL:
统一资源定位系统(uniform resource locator;URL)是因特网的万维网服务程序上用于指定信息位置的表示方法。简单来讲就是网址,网址本质是由IP和PORT组成的

    IP0+PORT:能够定位全世界独一无二的一台计算机上面的某一个应用程序

    域名解析:将网址解析成IP+PORT

    我们之所以不直接用IP+PORT的原因是因为太难记,所以发明了域名(网址)
    IP:PORT  实际使用冒号连接
    114.55.205.139:80


1.传输层协议(也称为TCP协议或可靠协议)是为了在不可靠的互联网上一共可靠的端到端字节流而专门设计的一个传输协议,数据不易丢失
"""
造成数据库不易丢失不是因为有双向通道而是因为有反馈机制,机制如下:给对方发送消息之后会建立一个副本,确保对象收到消息并且回复之后才会删除,否则在一定时间内会反复发送
"""

2.洪水攻击:同一时间内有大量客户请求建立连接,会导致服务端一直处于SYN_RECV状态,遇到洪水攻击有效的解决手段是建立缓冲池。

3.服务端如何区分客户建立连接的请求:可以对客户端作唯一标识

4.三次握手建立连接:
    4.1当应用程序通过TCP与另一个应用程序通信时,它会发出一个通信请求,询问能否与服务端进行连接,这个数据包称为SYN包。
    4.2如果服务端同意则回复一个SYN+ACK包
    4.3客户端收到之后回复一个ACK包,建立连接
"""
三次握手的原因在于:假设采取两次握手建立连接,因为某些错误的SYN1包没有到达服务器,在中间某个节点滞留,此时客户端会重新发送SYN包,这次数据正常送达,服务端回复SYN+ACK建立连接,但是突然第一包的SYN到达服务器,这时服务端会认为是客户端重新发起的依次连接。
"""

5.四次握手断连接:
    5.1当客户端没有消息要发送给服务端,有客户端首先发送syn消息请求断开通道。
    5.2服务端回复ACK包,无条件同意断开请求
    5.3服务端此时还可以发送未发送的数据,客户端也可以接收数据,待客户端发送完数据后,向客户端发送syn包,进入最后确认状态
    5.4客户端收到之后回复ACK包,此时连接关闭


1.也称为数据报协议,不可靠协议,早期的QQ使用的是UPD协议(不加任何额外功能)使用UDP的原因就是因为很简单、快捷、粗暴、只要指定对方的地址就可以发消息了。


应用层相当于是程序员自己写的应用程序 里面的协议非常的多
常见的有:HTTP、HTTPS、FTP


1.如果我们需要编写基于网络进行数据交互的程序,意味着我们需要自己通过代码来控制我们之前所学习的OSI协议。socket类似于操作系统,封装了丑陋复杂的接口提供简单快捷的接口。
Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
socket也叫套接字
    基于文件类型的套接字家族(单机):AF_UNIX
    基于网络类型的套接字家族(联网:AF_INET


sever.listen(5)

当有很多个客户端的情况下,我们可以设置等待数量(不考虑并发问题)
假设服务端只有一个人的情况下,在测试半连接池的时候,可以不用input获取消息,直接把消息写死即可


1.黏包现象产生的背景:
    1.1 服务端练习执行三次recv
    1.2 客户端连续执行三次send
执行上述操作之后发现,本来分三次打印的数据竟然一次都打完了,后面两次什么都没打印,该现象称为'黏包现象'

2.黏包现象产生的原因:
    2.1 谁也不知道每次接受的数据有多大,所以我们recv()括号内的数字1024肯定是偏大的,有时候一次接受的文字少,1024又偏小,所以我们无法判断具体的大小是多少。
    2.2 TCP也被称为流式协议:数据像水流不一样他没有间断,所以我们无法从中断开,TCP会针对数据量较小且发送间隔较短的多条数据一次性合并打包发送。

3.黏包现象如何避免:我们需要拿到一个动态的接收长度写在recv()括号内


1.struct模块可以将非固定长度的数据转化为固定长度(len = 4),语法结构为:struct.pack('i', len(数据变量名)),其中'i'是固定的参数。打包的过程也称为'报头'。

2.打包之后如果还需要使用原来的长度,需要用到struct.unpack('i', 数据变量名)来解压,解压后的数据类型是只有一个元素的元组,要拿到原长度需要用索引值拿
import struct

info = b'unbelievible'
print(len(info))  # 12
res = struct.pack('i', len(info))
print(len(res))  # 4

res_len = struct.unpack('i', res)
print(res_len)  # (12,)

3.思路到这里,我们可以构想出黏包现象的解决思路:
    1.将真实数据 转成bytes类型并计算长度
    2.利用struct模块将真实长度
    3.发送固定长度的报头
    4.发送字典数据
    5.发送真实数据

服务端:
    1.接收固定长度的字典报头
    2.利用struct模块反向解析出字典的长度
    3.根据字典真实的长度接收字典数据并处理成字典
    4.接收字典的真实数据


需求:不同电脑通过struct模块发送文件
服务端:
import socket
import struct
import json

sever = socket.socket()
sever.bind(('127.0.0.1', 8080))
sever.listen(5)
sock, addr = sever.accept()
# 1.接收固定长度的字典报头
data_dict_head = sock.recv(4)
# 2.根据报头解析出字典数据的长度
data_dict_len = struct.unpack('i', data_dict_head)[0]
# 3.接收字典数据
data_dict_bytes = sock.recv(data_dict_len)
data_dict = json.loads(data_dict_bytes)
# 4.获取真实数据的各项信息
total_size = data_dict.get('file_size')
with open(data_dict.get('file_name'), 'wb')as f:
    f.write(sock.recv(total_size))

客户端:
import socket
import os
import json
import struct

client = socket.socket()
client.connect(('127.0.0.1', 8080))
# 1.获取真实数据大小
file_size = os.path.getsize(r'D:\上海python金牌班\20221117 day41 黏包现象、并行与并发\11111.txt')
data_dict = {
    'flie_name': '11111.txt',
    'file_size': file_size,
    'file_desc': '内容很多,请耐心看完',
    'file_info': '私人珍藏'
}
# 3.制作字典报头
data_dict_bytes = json.dumps(data_dict).encode('utf8')
data_dict_len = struct.pack('i', len(data_dict_bytes))
# 4.发送字典报头,报头本身也是bytes类型
client.send(data_dict_len)
# 5.发送字典
client.send(data_dict_bytes)
with open(r'D:\上海python金牌班\20221117 day41 黏包现象、并行与并发\11111.txt', 'rb') as f:
    for line in f:
        client.send(line)


1.UDP服务端和客户端'各自玩各自的',支持多个客户端
2.UDP不会出现多个消息发送合并
服务端代码:
import socket

sever = socket.socket(type=socket.SOCK_DGRAM)
sever.bind(('127.0.0.1', 8081))

while True:
    data, addr = sever.recvfrom(1024)
    print('客户端地址>>>:', addr)
    print('上述地址罚送的消息>>>:', data.decode('utf8'))
    msg = input('>>>').strip()
    sever.sendto(msg.encode('utf8'), addr)

客户端1:
import socket

client = socket.socket(type=socket.SOCK_DGRAM)
sever_addr = ('127.0.0.1', 8081)

while True:
    msg = input('>>>').strip()
    client.sendto(msg.encode('utf8'), sever_addr)
    data, addr = client.recvfrom(1024)
    print(data.decode('utf8'), addr)

客户端2:
import socket

client = socket.socket(type=socket.SOCK_DGRAM)
sever_addr = ('127.0.0.1', 8081)

while True:
    msg = input('>>>').strip()
    client.sendto(msg.encode('utf8'), sever_addr)
    data, addr = client.recvfrom(1024)
    print(data.decode('utf8'), addr)


研究网络编程其实就是在研究计算机的底层原理及发展史
'''计算机中真正干活的是CPU'''
计算机系统的发展史:
    1.阶段一:穿孔卡片操作:序员用穿孔卡片将输入传入计算机,该阶段工作方式有两个特点:1.用户独占全机;2.CPU利用率不高

     2.阶段二:联机批处理系统(磁带存储):主机与输入机之间增加一个存储设备——磁带,在运行于主机上的监督程序的自动控制下,计算机可自动完成:成批地把输入机上的用户作业读入磁带,依次把磁带上的用户作业读入主机内存并执行并把计算结果向输出机输出。但是效率依旧不高

     3.阶段三:脱机批处理系统:主机不是直接与慢速的输入/输出设备打交道,而是与速度相对较快的磁带机发生关系,有效缓解了主机与设备的矛盾。
不足:每次主机内存中仅存放一道作业,每当它运行期间发出输入/输出(I/O)请求后,高速的CPU便处于等待低速的I/O完成状态,致使CPU空闲。为改善CPU的利用率,又引入了多道程序系统。


'''默认一台计算机只有一个CPU'''
1.单道技术:所有的程序排队执行,过程中不能重合

2.多道技术:所谓的多道程序技术,就是指允许多个程序同时进入内存并运行。即同时把多个程序放入内存,并允许它们交替在CPU中运行,它们共享系统中的各种硬、软件资源。当一道程序因I/O请求而暂停运行时,CPU便立即运转去运行另一道程序。

3.多道技术详细:
    3.1 计算PU在两种情况下会切换(结束此程序,让另一个程序占用CPU)
        1 程序有IO操作
        '''IO指输入或者输出'''
          输入/输出操作:input、time.sleep、write
        2 程序长时间占用CPU
           雨露均沾,让每个程序都被CPU运行一下
    3.2 保存状态:CPU每次切换走之前都需要保存当前的工作状态,下次切换回来基于上次的进度继续执行
"""
相当于一个资本家开了一家饭店,但是只雇了一个服务员,资本家为了利益最大化,让该服务员为第一桌点菜(客人已经想好点什么菜的情况下),点完立即给第二桌倒水,倒完水立即给第三桌上菜,一刻不停歇。
"""


1.进程与程序的区别:
    程序:没有被运行的程序(代码)
    进程:正在运行的程序(有CPU才能运行)(程序需要在内存当中才能够被运行,所以进程都在内存中)

2.进程的调度算法:
    2.1 FCFS:先来先服务。例如有五个程序等待运行,谁先来的就先运行谁。但是对于运行时间较短的程序不友好,例如第一个来的程序耗时较长,第二个程序即使运行时间较短也只能等待第一个程序运行结束之后才能运行。

    2.2 短作业优先调度:优先运行时间最短的程序,运行时间长即使来得早也要排队。

    2.3 时间片轮转法+多级反馈队列(目前还在用):将时间均分,分给每个程序运行。如果有程序没有运行完,则会让该程序进入下一个队列,下一个队列每次分得的时间会更长,但是优先级越低。比如一个程序在第一层运行时没有运行完,被分到第二层继续运行,但此时如果有一个新的程序来到第一层,那么CPU会优先运行这一个程序,但当CPU再次运行第二层的程序时分得的时间更长,频率也会越低。


1.并行:多个进程同时执行,必须要有多个CPU参与,单个CPU无法实现并行。

2.并发:多个进程看上去像是同时执行,单个CPU可以实现,多个CPU肯定也可以。例如CPU在运行一个程序时遇到了IO状态,然后保存状态后去运行另一个程序,看上去是同时执行但其实并没有(联系多道技术)(并发量评估了程序同时服务客户端数量的能力)。
'''可以简单的理解为餐厅有条不紊的运转,实则只有一个服务员'''
"""
判断以下语句正确与否:某程序员写的程序可以实现14亿并行量:错误
并行需要多个CPU,若改为并发则正确
"""
>>>目前国内能执行最高的并发量的软件:12306


1.就绪态:当进程已备份包除CPU以外的所有必要的资源, 只要获得CPU即可执行,这时进程的状态称为就绪态。

2.运行态:当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。

3.阻塞态:进程运行过程中出现了IO操作,阻塞态无法直接进入运行态,需要先进入就绪态。


1.同步异步用来表达任务的提交方式

2.同步:同步是指一个进程在执行某个请求的时候,如果该请求需要一段时间才能返回信息,那么这个进程会一直等待下去,直到收到返回信息才继续执行下去。
'''代码执行到某行时,如果代码需要调用其他代码,那么就去其他文件执行代码,执行完之后再回来'''

3.异步:异步是指进程不需要一直等待下去,而是继续执行下面的操作,不管其他进程的状态,当有信息返回的时候会通知进程进行处理,这样就可以提高执行的效率了,即异步是我们发出的一个请求,该请求会在后台自动发出并获取数据,然后对数据进行处理,在此过程中,我们可以继续做其他操作,不管它怎么发出请求,不关心它怎么处理数据。

'''代码执行到某行时,如果代码需要调用其他代码,继续往下执行'''
"""
你和朋友去吃饭,你们到达饭店,点了一桌满汉全席。由于你们饿的不行,就在饭店等待厨师做好菜,等菜上桌。你和你的朋友吃完饭,付了钱,舒舒服服的去商场购物了,一次饭局就结束了。这就是同步调用。

如今,我们正处在互联网时代,当你们饿的时候,就打开饿了么,点了一桌满汉全席,支付了餐费,这时你就完成了点菜。商家接到了你的订单之后,就会马上开始安排(毕竟顾客就是上帝嘛)。现在你们无须默默等待,以免被饿所折磨,于是你们打开了京东,开始了新的购物。当饿了么小哥饭菜送到时,一签单就完事了,并且你们也完成了购物。这就是异步调用。
我们之前所写的代码都是同步调用。
"""


阻塞与非阻塞表达任务的执行状态

1.阻塞:任务处于阻塞态

2.非阻塞:任务处于就绪态、运行态


我们通常不会只看到上述单个词汇,而是会看到上述词汇的组合使用,含义举例说明:
1.同步阻塞:只运行这一个程序,提交任务之后,等待CPU回来继续运行,效率最低。
'''去银行排队,专心排队其他什么事情都不做'''

2.同步非阻塞:只运行这一个程序,提交完任务之后,可以原地做一些其他的事情。
'''还是用普通的汤锅,米放进去,然后去打游戏,过一会就来看一次 '''

3.异步阻塞:程序处于阻塞态,但是可以做一些其他操作。
'''去某宝淘了个电饭锅,饭熟了会自动跳闸的那种。米放进去,然后傻傻的看它怎么做饭的'''

4.异步非阻塞:提交完任务之后CPU马上去运行另一个程序,并且处于运行态或者就绪态,异步非阻塞效率最高。
'''米放进去,按下按钮,然后就去打游戏了,等到跳闸的时候就去吃饭。'''


1.两种方式:
    1.1 鼠标双击软件图标
    1.2 python代码

2.用python代码创建进程(方法一):
"""
不同的操作系统盘创建进程的底层原理不一样
windows:以导入模块的形式创建进程
linux:以拷贝代码的形式创建进程
"""
windows如果用代码创建进程,进程文件相当于导模块,会将原来的代码原封不动地拷贝一份到新进程,新进程执行到创建进程那一步会继续创建新进程,往复循环,直到报错。而苹果电脑创建的新进程会不包含创建新进程的那一步,所以苹果电脑创建新进程不会循环报错。windows代码及报错信息如下:
from multiprocessing import Process
import time

def task(name):
    print('task is running', name)
    time.sleep(3)
    print('task is over', name)

p1 = Process(target=task)
p1.start()
print('主进程')

"""
windows创建新进程是以导模块的形式创建,所以导模块的几步应该要加上验证是否是执行文件的:if __name__ == '__main__'。
"""
from multiprocessing import Process
import time

def task(name):
    print('task is running', name)
    time.sleep(3)
    print('task is over', name)

if __name__ == '__main__':
    p1 = Process(target=task,args=(111,))  # 创建一个子进程对象
    p1.start()  # 用子进程对象创建一个新的进程,并在该进程中执行task功能
    print('主进程')  # 执行结果:主进程 task is running 111 task is over 111
说明p1.start()生成一个新进程是一个异步操作,子进程中调用task函数,因为子进程拷贝代码需要时间,所以父进程中的print('主进程')会优先打印,打印结束之后会再执行子进程。

from multiprocessing import Process
import time

def task(name):
    print('task is running', name)
    time.sleep(3)
    print('task is over', name)

def func1():
    print('from func1')

if __name__ == '__main__':
    p1 = Process(target=task,args=(111,))
    p2 = Process(target=func1)
    p1.start()
    p2.start()
    print('主进程')
# 执行结果:主进程 task is running 111 from func1 task is over 111
一个父进程可以同时开多个子进程,子进程P1在读取的过程中先执行print('主进程'),再执行子进程p1中的print('task is running', name),此时子进程p1中还有部分代码未执行,由于要sleep3秒,此时先执行p2中的print('from func1'),最后执行print('task is over', name)。

3.用python代码创建进程(方法二):
from multiprocessing import Process
import time

class MyProcess(Process):
    def __init__(self, name, age):
        super().__init__()
        self.name = name
        self.age = age

    def run(self):
        print('run is running', self.name, self.age)
        time.sleep(3)
        print('run is over', self.name, self.age)

if __name__ == '__main__':
    obj = MyProcess('max', 25)
    obj.start()
    print('主进程')
#  主进程  run is running max 25  run is over max 25
该方法是利用类产生对象,然后用对象产生子进程的过程。还是先执行父进程中的print('主进程'),再执行子进程中的run is running max 25、run is over max 25。


'''同一台计算机上的多个进程数据是严格意义上的物理隔离(默认情况下)'''
money = 1000

def task():
    money = 666

if __name__ == '__main__':
    task()
    print(money)
#  1000 print(money)找的是全局名称空间中的money

money = 1000

def task():
    global money
    money = 666

if __name__ == '__main__':
    task()
    print(money)
#  666 global局部修改全局,全局名称空间中的money被改成666

from multiprocessing import Process
import time

money = 1000

def task():
    global money
    money = 666
    print('子进程中的money', money)  #  子进程中的money 666  

if __name__ == '__main__':
    p1 = Process(target=task)
    p1.start()
    time.sleep(3)
    print(money)
# 1000
"""
如果没有if __name__ == '__main__':及子代码,此时全局名称空间中的money已经被修改成666,局部名称空间中无money。但是要生成一个子进程,在子进程中调用task()函数,子进程中会调用全局名称空间中的money以及task()函数,task()函数执行修改的是子进程中全局名称空间中的money。task()函数体代码中查看的是子进程中的money,结果是666,说明已经修改。
"""

需求:如何实现子进程运行结束之后再运行主进程?
from multiprocessing import Process
import time

def task(name):
    print('%s is running' % name)
    time.sleep(3)
    print('%s is over' % name)

if __name__ == '__main__':
    p = Process(target=task, args=('max',))
    p.start()
    print('主进程')
# 主进程  max is running  max is over
"""
正常运行会先执行父进程的所有代码,再执行子代码,因为在子代码读取过程中CPU会先读取父代码并且执行
"""
from multiprocessing import Process
import time

def task(name):
    print('%s is running' % name)
    time.sleep(3)
    print('%s is over' % name)

if __name__ == '__main__':
    p = Process(target=task, args=('max',))
    p.start()
    time.sleep(4)
    print('主进程')
#  max is running  max is over 主进程
"""
上述代码实现了先执行子进程再执行父进程,依靠的是父进程休眠时间总是比子进程长一些。但这种方法的弊端在于我们不知道子进程什么时候会休眠几秒,使用这种方式存在很多不确定性
"""
from multiprocessing import Process
import time

def task(name):
    print('%s is running' % name)
    time.sleep(3)
    print('%s is over' % name)

if __name__ == '__main__':
    p = Process(target=task, args=('max',))
    p.start()
    p.join()
    print('主进程')
#  max is running  max is over 主进程
"""
join()方法不管子进程需要休眠多少秒,直到执行完行子进程,再执行父进程
"""
from multiprocessing import Process
import time

def task(name, n):
    print('%s is running' % name)
    time.sleep(n)
    print('%s is over' % name)

if __name__ == '__main__':
    p1 = Process(target=task, args=('max1',1))
    p2 = Process(target=task, args=('max2',2))
    p3 = Process(target=task, args=('max3', 3))
    start_time = time.time()
    p1.start()
    p2.start()
    p3.start()
    p1.join()
    p2.join()
    p3.join()
    print(time.time() - start_time)
"""
max1 is running
max2 is running
max3 is running
max1 is over
max2 is over
max3 is over
3.151333808898926
"""
"""
根据前六部打印顺序得知,第一个子进程先开始,不等睡眠结束就开始运行第二个,再第三个。start()连在一起说明无需等待上一个子进程结束就可以开始下一个。
"""
from multiprocessing import Process
import time

def task(name, n):
    print('%s is running' % name)
    time.sleep(n)
    print('%s is over' % name)

if __name__ == '__main__':
    p1 = Process(target=task, args=('max1',1))
    p2 = Process(target=task, args=('max2',2))
    p3 = Process(target=task, args=('max3', 3))
    start_time = time.time()
    p1.start()
    p1.join()
    p2.start()
    p2.join()
    p3.start()
    p3.join()
    print(time.time() - start_time)
"""
max1 is running
max1 is over
max2 is running
max2 is over
max3 is running
max3 is over
6.363624334335327
"""
"""
join()表示必须等子进程运行结束之后才能运行主进程,同时也表示上一个子进程结束之后才能运行下一个。
"""


IPC机制:同一台电脑上进程间通信
消息队列:存储数据的地方,所有人都可以存,也可以取,类似于一个仓库
"""
知识回顾:队列:先进先出;栈:先进后出
"""
from multiprocessing import Queue

q = Queue(3)
q.put(11)
print(q.full())  # False 判断队列是否已满
q.put(22)
q.put(33)
print(q.full())  # True
# q.put(44)  # 数量超过存放数量,不会报错,但是不会再执行下面的代码了,想要继续执行要把这一行注掉
print(q.get())  # 11
print(q.get())  # 22
print(q.empty())  # False 判断列表是否为空
print(q.get())
print(q.get_nowait())  # 拿完了,用get_nowait()继续拿会直接报错
"""
full(), empty()在多进程中不能使用,因为判断的上一刻列表数据可能就发生变化。聊天软件中的消息即使不上线也可以收到,因为消息存放在消息队列中。
"""


上述知识点我们得知了队列可以当做一个仓库来存放数据,那么队列是否可以当做不同进程之间数据联系的仓库呢?
from multiprocessing import Process, Queue

def consumer(q):
    '''在子进程中拿出数据'''
    print('子进程从队列当中拿到的数据:', q.get())

if __name__ == '__main__':
    q = Queue()
    '''在主进程中添加数据'''
    q.put('主进程添加的数据a')
    p1 = Process(target=consumer, args=(q,))
    p1.start()
    p1.join()
    print('主进程')
'''
子进程从队列当中拿到的数据: 主进程添加的数据a
主进程
'''
"""
上述代码我们得出结论:主进程和子进程可以依靠队列来进行数据交互
"""

from multiprocessing import Process, Queue

def product(q):
    q.put('子进程p2添加进队列的数据a')

def consumer(q):
    print('子进程p1从队列中拿到的数据:', q.get())

if __name__ == '__main__':
    q = Queue()
    p1 = Process(target=consumer, args=(q,))
    p2 = Process(target=product, args=(q,))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print('主进程')
'''
子进程p1从队列中拿到的数据: 子进程p2添加进队列的数据a
主进程
'''
"""
不仅子进程和主进程之间可以通过队列数据交互,子进程自检也可以进行数据交互
"""


1.生产者:负责生产数据的'人';eg:爬虫爬取到的数据写入excel的过程。

2.消费者:负责处理数据的'人'。
"""
该模型除了上述两种类型,还应该有消息队列,生产者和消费者无直接关联,消息队列可以让生产者和消费者产生关联,并且可以对生产者产生的数据进行存储,也避免了两者需要同时工作的现象。消息队列也可以是数据库,文件,只要是能提供保存和提取服务的都可以
"""


1.在cmd窗口输入tasklist能看到系统给每一个进程分配的进程号,进程号和端口号一样都是为了方便管理
from multiprocessing import Process, current_process

def task():
   print(current_process())
   print(current_process().pid) # 获取子进程进程号

if __name__ == '__main__':
   print(current_process())
   print(current_process().pid) # 获取主进程进程号
   p1 = Process(target=task)
   p1.start()
   p1.join()
   print('主进程')
'''
<_MainProcess(MainProcess, started)>
48692
<Process(Process-1, started)>
49216
主进程
'''

2.获取进程号也可以选用os模块中的os.getpid()方法,os.getppid()是查看父进程的进程号,其中子进程的父进程号和主进程号一致,因为主进程就是子进程的父进程。主进程的父进程是pycharm的进程号。
import os
from multiprocessing import Process

def task():
   print('子进程:', os.getpid())
   print('子进程的父进程', os.getppid())

if __name__ == '__main__':
   p1 = Process(target=task)
   p1.start()
   p1.join()
   print('主进程', os.getpid())
   print('主进程的父进程', os.getppid())

'''
子进程: 47784
子进程的父进程 46788
主进程 46788
主进程的父进程 2976
'''
3.杀死进程:在pycharm中可以用子进程对象.terminate()结束。
import os
from multiprocessing import Process

def task():
    print('子进程:', os.getpid())
    print('子进程的父进程', os.getppid())

if __name__ == '__main__':
    p1 = Process(target=task)
    p1.start()
    p1.terminate()
    print('主进程', os.getpid())
    print('主进程的父进程', os.getppid())

'''
子进程: 47784
子进程的父进程 46788
主进程 46788
主进程的父进程 2976
'''
"""
也可以在cmd窗口用taskkill /F /PID 进程号 来终止
"""
4.判断进程对象时候出存活:
def task():
    print('子进程:', os.getpid())

if __name__ == '__main__':
    p1 = Process(target=task)
    p1.start()
    print(p1.is_alive())  # True
    p1.terminate()
    print(p1.is_alive())  # True
"""
由于终止后程序还在运行,只需要让它睡一下,就会发现已经不再存活
"""
import os
from multiprocessing import Process
import time

def task():
    print('子进程:', os.getpid())

if __name__ == '__main__':
    p1 = Process(target=task)
    p1.start()
    print(p1.is_alive())  # True
    p1.terminate()
    time.sleep(1)
    print(p1.is_alive())  # False


进程如果有守护进程,它关闭了那么它的守护进程也关闭
from multiprocessing import Process
import time

def task(name):
    print('%s是子进程' % name)
    time.sleep(3)
    print('%s是子进程'% name)

if __name__ == '__main__':
    p1 = Process(target=task, args=('进程1',))
    p1.daemon = True  # p1进程是主进程的守护进程
    p1.start()
    time.sleep(1)
    print('主进程')  # 主进程执行完毕,守护进程还没执行完,就已经结束
'''
进程1是子进程
主进程
'''


1.僵尸进程:进程执行完毕后并不会立刻销毁所有的数据,会有一些信息短暂保留下来,比如进程号、进程执行时间、进程消耗功率等给父进程查看
ps:所有的进程都会变成僵尸进程

2.孤儿进程:子进程正常运行,父进程意外死亡,操作系统针对孤儿进程会派遣福利院管理。


模拟抢票软件

from multiprocessing import Process
import time
import json
import random

# 查票
def search(name):
    with open(r'data.json', 'r', encoding='utf8') as f:
        data = json.load(f)
    print('%s在查票 当前余票为:%s' % (name, data.get('ticket_num')))

# 买票
def buy(name):
    # 再次确认票
    with open(r'data.json', 'r', encoding='utf8') as f:
        data = json.load(f)
    # 模拟网络延迟
    time.sleep(random.randint(1, 3))
    # 判断是否有票 有就买
    if data.get('ticket_num') > 0:
        data['ticket_num'] -= 1
        with open(r'data.json', 'w', encoding='utf8') as f:
            json.dump(data, f)
        print('%s买票成功' % name)
    else:
        print('%s很倒霉 没有抢到票' % name)

def run(name):
    search(name)
    buy(name)

if __name__ == '__main__':
    for i in range(10):
        p = Process(target=run, args=('用户%s'%i, ))
        p.start()

"""
多进程操作数据很可能会造成数据错乱>>>:互斥锁
    互斥锁
        将并发变成串行 牺牲了效率但是保障了数据的安全
"""