多道技术与并发
作者:互联网
多道技术与并发
目录1、UDP代码编写(了解内容)
1、UDP代码编写
import socket # sock模块在创建时,默认使用TCP协议,UDP协议时不需要创建通道的
udp_sk = socket.socket(type=socket.SOCK_DGRAM) # UDP协议
udp_sk.bind(('127.0.0.1',9000)) # 绑定地址
msg,addr = udp_sk.recvfrom(1024)
udp_sk.sendto(b'hi',addr)
udp_sk.close()
import socket
ip_port=('127.0.0.1',9000)
udp_sk=socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello',ip_port)
back_msg,addr=udp_sk.recvfrom(1024)
print(back_msg.decode('utf-8'),addr)
2、时间服务器
时间服务器的实现原理(我们电脑在联网之后时间会自动校准)
1、内部的小电容供电(可以理解为我们平时用的电子手表里的电池)
2、远程时间同步
3、简易qq程序(基于UDP协议实现)
server端:
#_*_coding:utf-8_*_
import socket
ip_port=('127.0.0.1',8081)
udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udp_server_sock.bind(ip_port)
while True:
qq_msg,addr=udp_server_sock.recvfrom(1024)
print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))
back_msg=input('回复消息: ').strip()
udp_server_sock.sendto(back_msg.encode('utf-8'),addr)
client端:
#_*_coding:utf-8_*_
import socket
BUFSIZE=1024
udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
qq_name_dic={
'tom':('127.0.0.1',8081),
'tony':('127.0.0.1',8081),
'egg':('127.0.0.1',8081),
'kk':('127.0.0.1',8081),
}
while True:
qq_name=input('请选择聊天对象: ').strip()
while True:
msg=input('请输入消息,回车发送,输入q结束和他的聊天: ').strip()
if msg == 'q':break
if not msg or not qq_name or qq_name not in qq_name_dic:continue
udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])
back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)
print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))
udp_client_socket.close()
2、计算机核心理论(操作系统发展史)
'''学习并发编程其实就是在学习操作系统的发展史'''
操作系统的发展史
1、穿孔卡片时代
CPU的利用率极低
2、联机批处理系统
将多个程序员的程序一次性路录入到磁盘中,之后交由输入机输入并由CPU执行
3、脱机批处理系统
现代计算机的雏形(远程输入,高速磁带,主机),高速磁带就类似于我们现在电脑的内存,
之所以叫告诉磁带,是因为CP的执行效率相当之快,而CPU处理数据遵循木桶效应,
简单理解为根据读取速度最慢的那个来执行,为了能提高CPU的执行效率,
因此采用了高速磁盘这个概念
3、多道技术(并发编程核心理论)
谈论多道技术的前提是单核CPU(单核CPU的概念即同一时间只能处理一个事件)
在谈多道技术之前,先对CPU的工作机制进行一定的了解
# CPU的工作机制
1、当某个程序进入IO状态的时候,操作系统会自动剥夺该程序的CPU执行权限
2、当某个程序长时间占用CPU的时候,操作系统也会剥夺该程序的CPU执行权限
1、多道技术的核心理论:
切换+保存状态
"""
所谓的切换+保存状态,即当当前程序进入IO状态或者长时间占用CPU的时候,
操作系统剥夺该程序的CPU执行权限,并记录当前程序的运行位置,转而用极快
的速度去启动另外一个程序,即切换CPU的执行对象,这就是所谓多道技术里的切换+保存状态
"""
2、并行与并发
并行:所谓的并行指定就是多个程序同时执行
并发:多个程序只要在极短的事件内同时执行,即看起来像同时执行即为并发
那么:
# 问题一:单核CPU能不能实现并行?
答案肯定是不行,因为单核CPU在一个时间点只能执行一个程序,但是单核CPU可以实现并发
# 问题二:123.6可以同一时间支持上亿用户进行狗牌,是并行还是并发
答案肯定是并发,并且是高并发
所谓的高并发指的是尽可能的占用所有的CPU去处理
而高并发的由来:星轨:微博可以支持八个星轨(即同一时间可以处理大量的数据访问请求)
- 多道技术的初级理念
eg:假设在电脑上同时启动三个事件:事件A、事件B、事件C
执行效果1:
执行效果2:
4、进程理论(进程调度核心理论)
进程与程序的区别
程序:一堆代码(死的),是永久的
进程:正在运行的程序(活的),暂时的
# 前提:单核CPU
在涉及到多个进程同时启动的时候,CPU是按照怎样的顺序去工作的,这里涉及到了进程的调度问题
# 进程调度
1、进程调度算法的演变
1、FCFS 先来先服务算法
优点:根据进程的启动顺序去服务
缺点:当前面一个进程消耗的事件很久,后面的进程作业只需要很短的时间时,
这种算法就不试用了,对短作业不友好
2、短期作业优先调度算法
优点:根据启动一个进程消耗时间的长短,CPU优先服务时间短的
缺点:但是当一个长作业前面有很多的短作业时,这时候就对长作业不友好了
3、时间片轮转法+多级反馈队列
时间片轮转法:将时间段分为多个相同的片段,当多个进程同时启动的时候,
给每个进程分配相同数量的时间片,根据多级反馈队列及多道技术
的切换+保存状态让CPU服务,
多级反馈队列:可以理解为将分配时间片数量的多少分为几个级别,从上至下,
分配时间片的数量逐渐增加,即越往下的进程消耗的时间越多。
总结:先分配给新的多个进程相同的时间片,
之后根据进程消耗的时间片多少分类别,越往下,先级越小
# 进程三状态图
就绪态、运行态、阻塞态
注:进程要想进入运行态必须先经过就绪态
# 同步与异步:用于描述任务的提交方式
同步:提交完任务之后原地等待任务的返回结果,期间不做任何事
异步:提交完任务之后不愿地等待任务的返回结果,直接去做其他事,结果由反馈机制自动提醒
# 示例:以洗衣服为例
同步:用洗衣机洗衣服时,洗衣机开始工作时,什么也不做,等着衣服洗好
异步:用洗衣机洗衣服时,洗衣机开始工作时,在洗衣机工作期间,可以去做一些其他的事,比如做饭,打扫卫生等等,等洗衣机工作完成之后会有一个反馈机制提醒我们衣服洗好了。
# 阻塞与非阻塞:用于描述任务的执行状态
阻塞:阻塞态
非阻塞:就绪态、运行态
时间片轮转法+多级反馈队列(结合多道技术的切换+保存状态)
进程三状态图
进程三状态代码演示
5、代码层面创建进程
# 代码层面创建进程
"""
在windows中,使用代码开设继承类似于平常导模块的操作:
在导入模块时,会优先从上到下执行模块里的代码,开设进程也是如此
所以在开设进程时,一定要在__main__判断语句内执行开设进程的语句,
否则就会出现循环开设进程的情况,导致报错
在linux中,是直接将代码完整的复制一份执行
不需要在__main__判断语句内执行
"""
1、创建进程
# 导入Process模块
from multiprocessing import Process
import time
import os
# 子进程与父进程:
父进程:当前执行的py文件
子进程:通过当前py文件创建子进程
# 方法一:使用函数来创建进程
def test(name):
print(os.getpid()) # 获取子进程号
print(os.getppid()) # 获取父进程号
print('%s开始执行' % name)
time.sleep(2)
print('%s结束执行' % name)
if __name__ == '__main__':
p = Process(target=test, args=('进程1',)) # 生成进程对象
p.start() # 告诉操作系统开设一个进程,采用异步提交
print(os.getpid()) # 获取父进程号
print('主进程')
# 方法二:使用面向对象的继承思想创建进程
# 导入Process模块
from multiprocessing import Process
import time
import os
class MyProcess(Process):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print(os.getpid()) # 获取子进程号
print(os.getppid()) # 获取父进程号
print('%s开始执行' % self.name)
time.sleep(2)
print('%s结束执行' % self.name)
if __name__ == '__main__':
p = MyProcess('进程1') # 生成进程对象
p.start() # 告诉操作系统开设一个进程,采用异步提交
print(os.getpid()) # 获取父进程号
print('主进程')
"""
总结:创建进程就是在内存空间中申请一块内存空间,将要运行的代码放到内存空间里,
一个进程对应在内存中占用一块独立的内存空间,
多个进程对应在内存中占用多个独立的内存空间,
即进程于进程之间的数据是隔离的,默认情况下是无法进行数据交互的
"""
# 执行结果:
27640 # 当前进程号
主进程
17412 # 当前进程号
27640 # 主进程号
进程1开始执行
进程1结束执行
# 发生上面现象的原因
"""
主进程在开设完子进程以后会继续往下执行主进程,
当主进程执行完毕之后才会去执行子进程,因此是采用的异步提交的方式
"""
6、进程join方法
- 进程join方法创建单个子进程
# 进程join方法:
执行完子进程后再执行主进程
# 代码实现
# 导入Process模块
from multiprocessing import Process
import time
import os
# 子进程与父进程:
# 父进程:当前执行的py文件
# 子进程:通过当前py文件创建子进程
# 方法一:使用函数来创建进程
def test(name):
print(os.getpid()) # 获取子进程号
print(os.getppid()) # 获取父进程号
print('%s开始执行' % name)
time.sleep(2)
print('%s结束执行' % name)
if __name__ == '__main__':
p = Process(target=test, args=('进程1',)) # 生成进程对象
p.start() # 告诉操作系统开设一个进程,采用异步提交
p.join() # 执行完子进程代码后再执行主进程代码
print(os.getppid()) # 获取父进程号
print('主进程')
# 执行结果:
14788 当前进程号
2180 父进程号
进程1开始执行
进程1结束执行
2180 当前进程号
主进程
- 创建多个子进程
# 代码实现
# 导入Process模块
from multiprocessing import Process
import time
# 方法一:使用函数来创建进程
def test(name):
print('%s开始执行' % name)
time.sleep(2)
print('%s结束执行' % name)
if __name__ == '__main__':
p1 = Process(target=test, args=('进程1',)) # 生成进程对象
p2 = Process(target=test, args=('进程2',)) # 生成进程对象
p3 = Process(target=test, args=('进程3',)) # 生成进程对象
p1.start() # 告诉操作系统开设一个进程,采用异步提交
p2.start() # 告诉操作系统开设一个进程,采用异步提交
p3.start() # 告诉操作系统开设一个进程,采用异步提交
print('主进程')
# 执行结果:
主进程
进程2开始执行
进程1开始执行
进程3开始执行
进程1结束执行
进程3结束执行
进程2结束执行
# 原因:
"""
start()的作用仅仅是告诉操作系统要开设子进程,
至于子进程的执行顺序,是随机的,
因为开设进程是在极短的时间内执行的
"""
- 并发的实现
# 代码实现
# 导入Process模块
from multiprocessing import Process
import time
def test(name, n):
print('%s开始执行' % name)
time.sleep(n)
print('%s结束执行' % name)
if __name__ == '__main__':
p_list = []
start_time = time.time() # 获取当前时间戳
for i in range(1, 4):
p = Process(target=test, args=(i, i))
p.start()
p_list.append(p) # 在不遇到join方法时,代码会继续放下执行
for p in p_list:
p.join()
process_time = time.time()-start_time # 获取三个子进程执行完后的时间
print('所有子进程结束执行的总时间:%s'% process_time)
print('主进程')
# 执行结果:
1开始执行
2开始执行
3开始执行
1结束执行
2结束执行
3结束执行
所有子进程结束执行的总时间:3.1132278442382812
主进程
# 出现上述情况的原因:
"""
1、执行结果的顺序问题:
start()的作用仅仅是告诉操作系统要开设子进程,开设完子进程以后剩下的操作都是操作系统内部进行的操作
没有join方法时,代码正常往下执行,遇到join方法,代码停留在原地,去执行子进程的代码。
在将进程开设完毕后才开始执行下一个for循环去执行join方法。
而且进程存在于列表里,列表是有序的,所以在便利列表执行子进程时的顺序是固定的。
2、总的运行时间为3s+的原因:
在开设完子进程以后,第一次执行p.join()方法,进程1执行了1s,
此时,进程2和进程3在执行进程1的时候也执行了1s,第二次执行 p.join()
方法,进程2总共需要执行2s,由于在执行进程1时已经执行了1s,因此,
在第二次执行p.join()方法时,进程2只执行了1s,在第二次执行p.join()的1s时,
进程3也执行了1s,加上第一次执行p.join()方法时执行的1s,进程3已执行了2s,
在第二次执行p.join()方法时,进程2结束;在第三次执行p.join()方法时,由于进程3
在前两次执行p.join()方法时已经执行了2s,所以进程3在第三次执行也只用了1s,加上
代码执行的时间,总时长为3s+,这也就是并发编程的核心思想,多道技术的核心思想:切换+保存状态
"""
总结:以上解释可以归纳为一句话
当多个进程同时启动时,消耗的总时长为时间最长的进程所消耗的时间多一点点
- 串行的实现(单道技术)
# 代码实现
# 导入Process模块
from multiprocessing import Process
import time
# 方法一:使用函数来创建进程
def test(name, n):
print('%s开始执行' % name)
time.sleep(n)
print('%s结束执行' % name)
if __name__ == '__main__':
start_time = time.time() # 获取当前时间戳
for i in range(1, 4):
p = Process(target=test, args=(i, i))
p.start()
p.join()
process_time = time.time()-start_time # 获取三个子进程执行完后的时间
print('所有子进程结束执行的总时间:%s'% process_time)
print('主进程')
# 执行结果
1开始执行
1结束执行
2开始执行
2结束执行
3开始执行
3结束执行
所有子进程结束执行的总时间:6.269834518432617
主进程
# 出现上述情况的原因:
"""
由于遇到join方法即停止往下运行,转而运行子进程的代码,所以
join方法和start方法同时写入for循环时,执行过程时,先将进程1启动并执行
结束以后再进行下一次循环,因此总的执行时间为1s+2s+3s+= 6s+(6秒多)
"""
7、进程间的数据隔离
# 如何证明多个进程间的数据是相互隔离的?
# 代码实现
# 导入模块
from multiprocessing import Process
# 定义一个全局变量
num = 100
def test():
global num # 使用global关键字声明在局部修改全局
num = 55 # 局部修改全局变量
print(num)
if __name__ == '__main__':
p = Process(target=test) # 生成子进程对象
p.start() # 告知操作系统要创建一个进程
p.join() # 执行完子进程代码再执行主进程代码
print(num)
# 执行结果:
55
100
"""
上述执行结果就证明了进程之间的数据是相互隔离的,进程里修
改的num仅仅是修改的子进程自己里的num,父进程里的num不受影响
"""
8、进程对象的相关方法
1、current_process 查看进程号(需要导入current_process模块,采用current_process().pid方法查看)
2、os.getpid()查看进程号
3、os.getppid() 查看父进程进程号
4、p.name 查看进程的名字,默认就有,也可以在实例化对象的时候通过关键字传入的方式传入name=''
5、p.terminate() 杀死子进程
6、p.is_alive() 判断进程是否存活,但会True或者False
注:在执行杀死子进程后紧接着执行判断进程是否存活,看不出结果,这是因为执行杀死进程后,操作系统会有一个反应时间,在执行完杀死进程后让主进程睡眠0.1秒即可看出效果
# 代码实现
# 导入模块
from multiprocessing import Process, current_process
import time
# 定义一个全局变量
num = 100
def test():
global num # 使用global关键字声明在局部修改全局
num = 55 # 局部修改全局变量
print(current_process().pid) # 查看当前进程进程号
if __name__ == '__main__':
p = Process(target=test) # 生成子进程对象
p.start() # 告知操作系统要创建一个进程
p.join() # 执行完子进程代码再执行主进程代码
p.terminate() # 杀死子进程
time.sleep(0.1) # 主进程睡眠0.1秒
print(p.is_alive()) # 查看进程存活状态 返回True/False
print(current_process().pid) # 查看当前进程进程号
标签:__,name,print,技术,并发,time,进程,执行,多道 来源: https://www.cnblogs.com/PyLy/p/15800366.html