Python之并发编程(进程)

文章目录

  • 一、操作系统的发展史
  • 二、进程基础(操作系统中的概念)
    • 1.什么是进程
    • 2.进程的调度算法
    • 3.进程的并行与并发
    • 4.进程的三状态
    • 5.同步异步
    • 6.阻塞与非阻塞
    • 7.同步异步与阻塞非阻塞综合使用
  • 三、如何创建进程
    • Process的属性、方法
    • 如何开启多进程
    • 进程间的数据默认隔离
    • 基于TCP协议的高并发程序

一、操作系统的发展史

	首先回顾三大核心硬件
		CPU 是计算机中那个真真一直在运行干活的人
		内存 给CPU准备需要运行的代码 软件
		硬盘 用来存储将可能要被运行的代码 软件

进入主题

	1.穿孔卡片
		1725年,法国人布乔发明了打孔卡,用于贮存纺织机工作过程控制的信息。
		但当时打孔卡并没有广泛应用。19世纪80年代,打孔卡主要用于记录数据,
		曾用于记录美国1890年人口普查数据。
		(CPU利用率非常的低 好处就是程序员可以一个人来独占计算机 想做什么就做什么)

Python之并发编程(进程)_第1张图片

	2.联机批处理系统
		加载在计算机上的一个系统软件,在它的控制下,计算机能够自动地、成批地处理
		一个或多个用户的作业(这作业包括程序、数据和命令)。
		缩短录入数据的时候 让CPU连续工作的时间变长(提升CPU利用率)

Python之并发编程(进程)_第2张图片

	3.脱机批处理系统
		为克服与缓解:高速主机与慢速外设的矛盾,提高CPU的利用率
		又引入了脱机批处理系统,即输入/输出脱离主机控制。
		(现代计算机的雏形主要提升CPU利用率)

Python之并发编程(进程)_第3张图片

	单道技术
		所有的程序排队等着被运行 总耗时是所有程序耗时之和
		(比如打游戏听歌>必须的听完歌等着播完>之后才能去打游戏)
		
	多道技术
		计算机利用空闲时间提前准备好一些数据 提高效率 总耗时较短
		(比如听歌>这一首歌还没唱完>就已经加载好了下一首歌的歌词歌曲)
	
	多道技术简单的来说就是 切换+保存状态
		1.CPU在两种下会切换(去执行其他的程序)
			1> 程序自身进入IO操作(IO:输入输出操作)
				例如:获取用户输入、time.sleep、读取文件、保存文件
		2.保存状态
			每次切换之前要记录下当前执行的状态 之后切换回来基于当前状态继续执行
			 	例如:打游戏撩妹 文字已经打入聊天框 游戏开始了 打完文字还在聊天框内

	eg:
	  一个人要同时使用五台打印机打印资料,请问如何要一个人连续使用打印机打印资料?
		启动第一台打印机打印资料之后,第一台在打印过程中就可以去启动第二台打印机执行打印任务,依次类推...

二、进程基础(操作系统中的概念)

进程(资源单位):一个正在运行的程序,操作系统中正在运行的应用程序。进程之间是相互物理隔离的。因为如果未进行隔离的话,如果这一个程序被植入病毒入侵,那么就可以通过这个进程进而其它的进程,所以进程之间是互相隔离的。进程之间无法直接通信,需要通过特定的技术

线程(执行单位):操作系统能够机械能运行调度的最小单位。它被包含在进程中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程执行不同的任务

进程内必须有一条或之上的线程,每条线程执行不同的任务

1.什么是进程

程序:一堆躺在文件夹里面的代码(还没有运行)

其实是一个死的东西,一堆代码就是程序,它没有生命周期

进程:正在被运行的程序(代码正在运行)

它是有生命周期,这个任务做完,进程就不存在了

举个例子形象的说明:

  • 计算机的核心是CPU,它承担了所有的计算任务,它就像一座工厂,时刻在运行
  • 假定工厂的电力有限,一次只能供给一个车间使用。也就是说,一个车间开工的时候,其他车间都必须停工。背后的含义就是,单个CPU一次只能运行一个任务。
  • 进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。
  • 一个车间里,可以有很多工人。他们协同完成一个任务。
  • 线程就好比车间里的工人。一个进程可以包括多个线程。

或者用这个来说明:
厨师做饭,厨师做一道菜、应该有菜谱,按照菜谱的程序一步一步的做,在这个过程中,菜谱就是程序,程序就是路程. 做菜的过程就是进程,厨师就是线程,如果做菜完毕了,进程就不存在了,线程是进程中实际做事儿的。

2.进程的调度算法

进程和线程都是由操作系统来调度使用的,我们程序员是不能控制的,这里就涉及到了调度算法

'进程的调度算法'
	先来先服务算法:
		见名知意 第一个打开的程序先运行(针对后面打开耗时较短的程序不友好)

	短作业优先调度
		查看里面用时最少的程序先运行(针对耗时比较长的程序不友好)
	
	时间片轮转法+多级反馈队列
		将固定的时间均分成很多份,所有的程序来了都公平的分一份
		分配多次之后如果还有程序需要运行,则将其分到下一层(保证后面的程序能运行)
		越往下表示程序总耗时越长,每次分的时间片越多,就优先级越低

3.进程的并行与并发

'并行:统一时刻同时运行'
	多个进程同时执行,必须要有多个CPU核心参与,单核CPU无法实现并行
	eg:
		如果CPU是单核的,同一时刻能不能够做到同时执行多个任务。答:不能
		如果是多核心的,同一时刻是能够做到的
		如果核心是2个,同一时刻最多执行两个任务
		如果核心是4个,同一时刻最多执行四个任务
	
'并发:一段时间内看起来是同时运行'
	多个进程看上去像同时执行,单核CPU可以实现,多核心CPU肯定也可以实现
	eg:看视频、听音乐、浏览网页同时操作,这个单核是可以达到的,CPU的切换达到的

4.进程的三状态

在程序运行的过程中,由于被操作系统的调度算法控制,程序会进入 几个状态:就绪态、运行态和阻塞态

Python之并发编程(进程)_第4张图片

就绪态

  • 当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。即所有的进程在被CPU执行之前都必须先进入就绪态等待。

运行态

  • 执行/运行(Running)状态当状态已获得处理机,其进程正在处理机上执行,此时的进程状态称为执行状态。

阻塞态

  • 阻塞(Blocked)状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如:等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。即进程运行过程中出来了IO操作,阻塞态无法直接进行运行态,需要先进入就绪态。

创建态和终止态:进程开始和结束的状态

代码演示:
Python之并发编程(进程)_第5张图片

5.同步异步

'同步异步主要用于描述任务的提交状态'
	同步:
		提交完成任务之后再原地等待任务的结果,期间不做任何事(单道技术)
		eg:去银行办理业务,选择排队等待(一直同步等待消息的通知)
	异步:
		提交完成任务之后,不在原地等待直接去做其他事情,结果自动提醒(多道技术)
		eg:取号排队,取号之后就可以做其他事,直到被窗口喊到取的号,再去柜台办理业务

6.阻塞与非阻塞

'阻塞与非阻塞主要用于描述进程的状态'
	阻塞(阻塞态)	等待运行完再接着下一步
	非阻塞(就绪态、运行态)	无需等待直接下一步
'''
当一个任务因调用或IO要获取某个结果,阻塞调用必须等待调用的结果,
非阻塞调用则可以不必等待这个结果而使进程始终处于就绪态或运行态
'''

7.同步异步与阻塞非阻塞综合使用

	同步阻塞:在银行排队,并且在队伍中什么事情都不做
	同步非阻塞:在银行排队,并且在队伍中做点其他事情
	异步阻塞:取号,在旁边座位上等着叫号,期间不可做事
	异步非阻塞:取号,在旁边座位上等着叫号,期间可以做别的事情
	'异步非阻塞效率最高'
'''
很多人会把同步和阻塞混淆,是因为很多时候同步操作会以阻塞的形式表示出来,
同样的,很多人也会把异步和非阻塞混淆,因为异步操作一般都不会再真正的IO操作处被阻塞。
'''

三、如何创建进程

'''
创建进程的两种方法:
    1.鼠标双击软件图标(打开程序 底部代码运行就是创建进程)
    2.代码创建进程(两种方法)  需要用到模块mutiprocessing、Process
'''

说明:
在不同的操作系统中创建进程底层原理不一样
	windows
	     以导入模块的形式创建进程,
	     需要用到if __name__ == '__main__':启动脚本(不然报错)
	      
	linux/mac
	     以拷贝代码的形式创建进程
	     不需要启动脚本 但是为了更好的兼容性 加上启动脚本更好


'''方法1:创建函数'''
from multiprocessing import Process   # 导入模块
import time

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


'''现在这个写法还没有开启进程'''
'''在windows平台上必须写在__main__里面'''
if __name__ == '__main__':
    'def __init__(self, group=None, target=None, name=None, args=(), kwargs={},'
    p1 = Process(target=task, kwargs={'name': 'tom', 'age': 19})  # 实例出来一个进程类,让这个进程执行task任务
    # p1 = Process(target=task,args=('jack',18))  # 位置传参
    p1.start()  # 真正的开启进程(异步)
    '''
    操作系统是负责把这个进程开起来
    开启一个进程来执行task任务,真正是谁执行的这个任务?是线程,进程里面至少有一个线程
    '''
    # task('chen',20)  # (同步)
    
    '进程的属性、方法'
    print(p1.name)  # 查看进程名称
    print(p1.pid) 	# 查看进程号
    
    print(p1.is_alive())  # 查看当前进程是否存活
    p.terminate() 	# 杀死进程
    
    '''
    	开启进程和杀死进程
       	只是通知操作系统去开进程,并不是立马把进程开起来,它需要消耗一定的时间,
       	侧面的反应了开启进程其实消耗很大
    '''
    
    print('执行主进程')

'''
运行结果为:
    同步方式下,先执行了task函数在进行主进程:
        task is running chen 20
        task is over chen 20
        主进程
    
    异步方式下:先执行了主进程,在进行了task函数
        主进程
        task is running tom 19
        task is over tom 19
'''


'''方法2:创建类'''
from multiprocessing import Process  # 导入模块
import time

class MyProcess(Process):   #  继承Process类
    def __init__(self,name):
        super().__init__()
        self.name = name

    def run(self):
        print('start run', self.name)
        time.sleep(1)  # 睡眠1秒后运行
        print('end run', self.name)


if __name__ == '__main__':
    obj = MyProcess('jack')
    obj.start()  # 启动子进程,只有对象才能去点子进程
    print('主进程')

'''底层原理都是,先运行完父类的代码,创建一个进程空间运行子类的代码'''

Process的属性、方法

	'''Process的几个方法'''
from multiprocessing import Process
import time


def task(name, age, gender):
    print(name, age, gender)
    print('子进程')


if __name__ == '__main__':
    '''
    只是通知操作系统去开进程,并不是立马把进程开起来,它需要消耗一定的时间,侧面的反应了开启进程其实消耗很大
    '''
    p = Process(target=task, args=(), kwargs={'name': 'chen', 'age': 18, 'gender': 'male'})
    # 实例化出来一个进程类,让这个进程执行task任务
    p.start()  # 真正的开启进程(异步)
    # 操作系统是负责把这个进程开起来,开启一个进程来执行task任务
    # 真正谁执行这个任务,是线程,进程里面至少要有一个线程
    '''进程的几个属性:1.进程名,2.进程号pid'''
    # 如何查看进程的名称
    # print(p.name)  # Process-1

    # 如何修改进程名称
    p.name = 'hello'
    print(p.name)  # hello

    # 如何查看进程号
    print(p.pid)  # Process pid

    print(p.is_alive())  # True
    p.terminate()  # 杀死进程,结束任务
    # 系统并不是第一时间立马就杀死进程,需要给点时间
    time.sleep(1)
    print(p.is_alive())  # False

    p.join()  # 等待子进程的代码全部执行完毕,在走主进程的
    print('主进程')

如何开启多进程

	'''如何开启多进程'''
# 开启多进程就意味着可以同时做多个任务,一个进程做一个任务,多个进程肯定是做多个任务
from multiprocessing import Process
import time

def task(name):
    print(name)
    print('子进程')
    time.sleep(1)

if __name__ == '__main__':
    '''理论上你是可以一直开进程,但是你需要考虑资源的消耗情况'''
    start_time = time.time()
    ll = []
    for i in range(3):
        p = Process(target=task, kwargs = {'name':'jack'})  # 实例化一个进程类
        p.start()
        # p.join()  # 放在里面执行是同步串行 
        ll.append(p)

    for i in ll:
        i.join()

    print('主进程,总时间:',time.time() - start_time)
    

'''这种形式和下面的一样的意思,都是异步并发'''
if __name__ == '__main__':
    '''理论上你是可以一直开进程,但是你需要考虑资源的消耗情况'''
    start_time = time.time()

    p1 = Process(target=task, kwargs = {'name':'jack'})  # 实例化一个进程类
    p2 = Process(target=task, kwargs = {'name':'tom'})  # 实例化一个进程类
    p3 = Process(target=task, kwargs = {'name':'chen'})  # 实例化一个进程类
    '''
    这种就是异步并发,
    同一个时刻执行所有的任务
    结果耗时为1秒多
    '''
    p1.start()
    p2.start()
    p3.start()
    p2.join()
    p1.join()
    p3.join()
    '''
    这种就是同步串行,
    会等上一个执行完毕才执行下一个
    结果耗时为3秒多
    '''
    # p1.start()
    # p1.join()
    # p2.start()
    # p2.join()
    # p3.start()
    # p3.join()

    print('主进程,总时间:',time.time() - start_time)

进程间的数据默认隔离

'''
进程间数据默认隔离
    多个进程数据彼此之间默认是互相隔离的(每个有各自的进程空间,互不干扰)
    如果真的想要交互,需要借助 管道和队列
'''
from multiprocessing import Process
money = 100  # 父进程的全局数据

def task():
    global money
    money = 666  # 子进程的局部money数据
    print('子进程打印的money:',money)


if __name__ == '__main__':
    p = Process(target= task,)
    p.start()
    p.join()  # 等待子进程运行完毕后执行父进程
    print('父进程打印的money:',money)
'''
最后得到的结果为:
    子进程打印的money: 666
    父进程打印的money: 100
所以证明了是互相隔离的,互不干扰
'''

Python之并发编程(进程)_第6张图片

基于TCP协议的高并发程序

import socket
'''一个服务端不能够同时给多个客户端发送数据'''
from multiprocessing import Process

def task(conn):
    while True:
        try:
            # 异常一个bug:粘包现象
            data = conn.recv(2048) # 括号里面写的是接收的字节数,最多接收2048个

            if len(data) == 0:
                continue
            print(data)

            conn.send(data.upper())
        except Exception as f:
            print(f)
            break

    conn.close()


if __name__ == '__main__':
    '''
    1.买手机
    # SOCK_STREAM  ====> 代表的是TCP协议
    # socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # udp协议
    '''
    server = socket.socket()  # 默认是TCP协议

    '''
    2.买手机卡
     # '0.0.0.0'  =====> 代表允许任何的ip链接
    # server.bind(('0.0.0.0', 8000)) # 服务端绑定一个地址
    '''
    server.bind(('127.0.0.1', 12306))  # 服务端绑定一个地址

    '''
    3.开机
    '''
    server.listen(1)  # 监听 半连接池
    print('服务端正在准备接收客户端消息:')
    while True:
        conn,addr = server.accept()
        # 接收,程序启动之后,会在accept这里夯住,阻塞
        p = Process(target = task, args=(conn,))
        p.start()


'''可以同时运行多个客户端并且实现并发'''
'''客户端'''
import socket

client = socket.socket()

# 连接服务端的IP地址和端口号
client.connect(('127.0.0.1',12306))
while True:
    # 发送数据给服务端
    mag = input('请输入发送的数据>>>>:').strip()
    client.send(mag.encode('utf-8'))

    # 接收服务端回应的数据 最大字节数
    data = client.recv(2048)
    print(f"接收都服务端的数据:",data.decode('utf-8'))

# 断开连接
client.close()

你可能感兴趣的:(网络编程,python)