多道技术与并发
2022/1/14 6:04:13
本文主要是介绍多道技术与并发,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
多道技术与并发
目录- 多道技术与并发
- 1、UDP代码编写(了解内容)
- 2、计算机核心理论(操作系统发展史)
- 3、多道技术(并发编程核心理论)
- 4、进程理论(进程调度核心理论)
- 5、代码层面创建进程
- 6、进程join方法
- 7、进程间的数据隔离
- 8、进程对象的相关方法
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) # 查看当前进程进程号
这篇关于多道技术与并发的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-24Java中定时任务实现方式及源码剖析
- 2024-11-24Java中定时任务实现方式及源码剖析
- 2024-11-24鸿蒙原生开发手记:03-元服务开发全流程(开发元服务,只需要看这一篇文章)
- 2024-11-24细说敏捷:敏捷四会之每日站会
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解