多道技术与并发

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:

image

执行效果2:
image

4、进程理论(进程调度核心理论)

进程与程序的区别

​ 程序:一堆代码(死的),是永久的

​ 进程:正在运行的程序(活的),暂时的

# 前提:单核CPU
	在涉及到多个进程同时启动的时候,CPU是按照怎样的顺序去工作的,这里涉及到了进程的调度问题
# 进程调度
	1、进程调度算法的演变
    	1、FCFS 先来先服务算法
		优点:根据进程的启动顺序去服务
		缺点:当前面一个进程消耗的事件很久,后面的进程作业只需要很短的时间时,
		这种算法就不试用了,对短作业不友好

        2、短期作业优先调度算法
		优点:根据启动一个进程消耗时间的长短,CPU优先服务时间短的
		缺点:但是当一个长作业前面有很多的短作业时,这时候就对长作业不友好了

        3、时间片轮转法+多级反馈队列

        	时间片轮转法:将时间段分为多个相同的片段,当多个进程同时启动的时候,
			    给每个进程分配相同数量的时间片,根据多级反馈队列及多道技术
			    的切换+保存状态让CPU服务,
		多级反馈队列:可以理解为将分配时间片数量的多少分为几个级别,从上至下,
			    分配时间片的数量逐渐增加,即越往下的进程消耗的时间越多。
           	总结:先分配给新的多个进程相同的时间片,
		     之后根据进程消耗的时间片多少分类别,越往下,先级越小

 # 进程三状态图
		就绪态、运行态、阻塞态
    		注:进程要想进入运行态必须先经过就绪态

 # 同步与异步:用于描述任务的提交方式
		同步:提交完任务之后原地等待任务的返回结果,期间不做任何事
    		异步:提交完任务之后不愿地等待任务的返回结果,直接去做其他事,结果由反馈机制自动提醒
        # 示例:以洗衣服为例
        	同步:用洗衣机洗衣服时,洗衣机开始工作时,什么也不做,等着衣服洗好
            	异步:用洗衣机洗衣服时,洗衣机开始工作时,在洗衣机工作期间,可以去做一些其他的事,比如做饭,打扫卫生等等,等洗衣机工作完成之后会有一个反馈机制提醒我们衣服洗好了。

 # 阻塞与非阻塞:用于描述任务的执行状态
	阻塞:阻塞态
    	非阻塞:就绪态、运行态

时间片轮转法+多级反馈队列(结合多道技术的切换+保存状态)

image

进程三状态图

image

进程三状态代码演示

image

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)  # 查看当前进程进程号


这篇关于多道技术与并发的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程