1、进程
- 概念;程序在一个数据集上的一次动态执行过程(运行中的程序,不运行就不叫进程)抽象概念
- 组成:
- 程序:编写的描述功能的代码
- 数据集:程序在执行过程中所需的资源
- 进程控制块:记录进程的外部特征,描述进程的执行变化过程,系统可以用它控制和管理进程,是系统感知进程存在的唯一标志
- 阐述:进程与进程之间占用的是独立的内存块,因此数据也是独立的
- 优点:同时利用多个cpu,能够进行多个操作
- 缺点:耗费资源(需重开辟内存空间)
- 打开一个程序至少会有一个进程,他是操作系统进行资源分配的基本单位
- j进程先执行主进程,再执行子进程,子进程间是无序的
- 控制台查看进程名字:tasklist
- 进程的状态:
- 就绪态:正在等待cpu执行
- 执行态:cpu正在执行其功能
- 等待态:等待某些条件满足
- 构造方法
#进程
Process(group,target,name.args.kwargs)
group:线程组,默认为None
target:要执行的方法(函数名)
name:进程名
args/kwargs: 要掺入方法的参数
- 实例方法
#实例方法
is_alive() 返回进程是否在运行
jion(timeout):(主进程等待子进程执行完)阻塞当前上下文环境中的进程,直到调用此方法的进程终止或达到指定的timeout(可选参数)
start():进程整备就绪,等待cpu调度 启动进程
run():start()调用run方法,如果实例进程时未制定target,start()执行默认run方法
terminate():不管任务是否完成,立即停止工作进程
- 属性:
daemon:和线程的setDaemon功能一样
name:当前进程的别名
pid:当前进程号
- 创建进程的方式
#方法一:调用模块创建进程
from multiprocessing import Process
import os
import time
def one():
time.sleep(5)
print(f'这是子进程one进程号{os.getpid()},父进程进程号为{os.getppid()}')
def two():
print(f'这是子进程two进程号{os.getpid()},父进程进程号为{os.getppid()}')
if __name__ == '__main__':
#创建进程
p1=Process(target=one,name='python进程')#
p2=Process(target=two)
#启动进程
p1.start()# 这是子进程one进程号1308,父进程进程号为8732
print('这是p1阻塞join前的状态',p1.is_alive())# 这是p1阻塞join前的状态 True
p1.join()
print(f'这是one,two的进程名{p1.name,p2.name}')# 这是one,two的进程名('python进程', 'Process-2')
print(f'p1的状态{p1.is_alive()},这是p2的状态{p2.is_alive()}')# 这是one,two的进程名('python进程', 'Process-2')
p2.start()# 这是子进程two进程号7028,父进程进程号为8732
#方法二 通过继承类的方式
from multiprocessing import Process
import os
class Jc(Process):
def run(self):
print(f'这是子进程进程号{os.getpid()}')
p=Jc()#实例化对象
if __name__ == '__main__':
print(p)# <Jc(Jc-1, initial)>
p.start()# 这是子进程进程号1908 进程号是不固定的
print('这是子进程的进程名',p.name)# 这是子进程的进程名 Jc-1
- 进程间的通信
- 进程间不共享全局变量
#进程间不共享全局变量
from multiprocessing import Process
li=[]
def write():
for i in range(4):
li.append(i)
print('这是写入的内容:',li)
def read():
print('这是读取到的内容:',li)
if __name__ == '__main__':
p1=Process(target=write)
p2=Process(target=read)
p1.start()# 这是写入的内容: [0, 1, 2, 3]
p1.join()
p2.start()# 这是读取到的内容: []
2. 进程间的通信
1. 创建进程模块下的队列(Queue)
1. 入队 q.put() 放入数据
2. 出队 q.get() 取出数据
3. 导入模块 from queue import Queue
4. 初始化一个队列对象 q=Queue(3) #最多接受三条信息
5. 队列个数 q.qsize()
6. 判断队列是否为空 q.empty() True为空
7. 进程里队列的使用
#进程中队列的使用
from multiprocessing import Process,Queue
import time
li=['齐天大圣','爱你一万年','月光宝盒']
def write(q1):
for i in li:
q1.put(i)#放入数据
time.sleep(0.2)
print('写入的数据个数为:',q1.qsize())
def read(q2):
while True:
if q2.empty():
break
else:
print('读取到的数据为',q2.get())
if __name__ == '__main__':
q =Queue()#创建队列队象 不限长度
p1=Process(target=write,args=(q,))
p2=Process(target=read,args=(q,))
p1.start()# 写入的数据个数为: 3
p1.join()
p2.start()# 读取到的数据为 齐天大圣 读取到的数据为 爱你一万年 读取到的数据为 月光宝盒
2. 管道(Pipe)
1. Pipe常用于两个子进程间的通信,两个进程分别位于管道的两端
2.Pipe方法返回(c1,c2)代表一个管道两个极端,Pipe方法有duplex参数,为 Ture(默认值),则是双工模式,及c1与c2均可收发,若为False,则c1只负责接收,c2只负责发送,send()和recv()分别是消息的发送与接收
3.在双工模式下,可以调用c1.send()发送消息,c1.recv()接收消息,若没有消息接收,recv方法则一直阻塞,拖管道关闭,则recv()会抛出EoFError异常
4.p=Pipe() 创建对象
5.p.send() 发送消息
6.p.recv() 接收消息
7.process.terminate() 可强制终止进程(防止死循环)
#进程通信之管道
from multiprocessing import Process,Pipe
import time
li=['一花异世界','一叶一菩提','大自在','观自在']
def write(p):
for i in li:
p.send(i)
print('发送消息为:',i)
time.sleep(0.2)
def read(p):
while True:
print('接收消息为',p.recv())
if __name__ == '__main__':
p=Pipe()#创建对象
p1=Process(target=write,args=(p[0],))
p2=Process(target=read,args=(p[1],))
p1.start()
p2.start()
p1.join()
p2.terminate()#强行终止进程,(死循环)
结果:
发送消息为: 一花异世界
接收消息为 一花异世界
发送消息为: 一叶一菩提
接收消息为 一叶一菩提
发送消息为: 大自在
接收消息为 大自在
发送消息为: 观自在
接收消息为 观自在
3. 队列(Queue)与管道(Pipe)都实现了进程间的通信,有一定的数据共享功能,但他们会阻塞进程,以下两种数据共享不会阻塞进程,并且都是多进程安全的Manager() 和 锁Lock
1. Manager()
1. python中进程间共享数据,处理基本的queue,pipe和value+array外还提供了更高层次的封装,使用multiprocess.Manager可以简单的使用这些高级接口
2. Manager()返回的manager对象控制了一个serve进程,此进程包含的python对象可以被其他的进程通过proxies来访问,从而达到多进程间数据通信且安全
3. Manager支持的类型有lis,dict,Namespace,Lock,RLock,Semaphore,BoundeSemaphore.Condition,Event,Queue,Value和Array
4. Manager的dict、list使用
#Manager之dict list
from multiprocessing import Process,Manager
import time,os
li=['时间','空间','人物']
def write(d,l):
for i in range(len(li)):
d[i]=li[i]#给生成的字典添加元素
l.append(i)#给生成的列表添加元素
time.sleep(0.2)
print(f'进程号{os.getpid()}写入数据为',d,l)
def read(d,l):
print(f'进程号{os.getpid()}读取的数据为',d)
time.sleep(0.2)
print(f'进程号{os.getpid()}读取到的列表数据为',l)
if __name__ == '__main__':
d=Manager().dict()#生成一个字典,可进程间使用
l=Manager().list()#生成一个列表
p1=Process(target=write,args=(d,l))#创建子进程
p2=Process(target=read,args=(d,l))
p1.start()# 进程号18924写入数据为 {0: '时间', 1: '空间', 2: '人物'} [0, 1, 2]
p1.join()#没有阻塞时,可能进程并发,竞争资源会报错
p2.start()# 进程号4060读取的数据为 {0: '时间', 1: '空间', 2: '人物'} 进程号4060读取到的列表数据为 [0, 1, 2]
p2.join()
5. Manager之共享内存的使用
1. 共享内存有两个结构,一个是Value,一个是Array,这两个结构内部都实现了锁机制,因此多进程是安全的
2. Vlaue和Array都需要设置其中存放值的类型,d是double,i是int,具体关系在python标准库的sharedctypes模块中查看
3. Value和Array,这些值在主进程中管理,很分散。
#manager之共享内存hjn
from multiprocessing import Process,Manager
import os,time
def write(douN,intN):
douN.value+=2
for i in range(4):
intN[i]=i+10
def read(douN,intN):
print('读取到的数据为',douN,intN)
if __name__ == '__main__':
with Manager() as manager:
douN=manager.Value("i",10)
intN=manager.Array("d",range(9))#此处参数是一个可迭代的序列
p1=Process(target=write,args=(douN,intN))
p2=Process(target=read,args=(douN,intN))
p1.start()
p1.join()
p2.start()# 读取到的数据为 Value('i', 12) array('d', [10.0, 11.0, 12.0, 13.0, 4.0, 5.0, 6.0, 7.0, 8.0])
p2.join()
3. 进程同步
1. 锁是为了确保数据的一致性
#进程同步之锁(Lock)
from multiprocessing import Process,Lock
import time
def run(i,lock):
# with lock:#自动获取锁及释放锁
if lock.acquire(1):#加锁 1 表示独占
time.sleep(1)
print(i)
lock.release()#解锁
if __name__ == '__main__':
lock=Lock()
for i in range(4):
p=Process(target=run,args=(i,lock,))
p.start()
- 进程池
- 1. 当需要创建的子进程不多时可以用process动态生成多个进程,但若是上百个上千个目标,就可以用到进程池了
- 2. 初始化Pool时,可以指定一个最大的进程数,当有新的请求提交到Pool时,若池还没满,那么就会创建新的进程来执行该请求
- 3. 若进程池中达到最大值该请求就会等待,直到池中有进程结束,才会用到之前的进程来执行新的任务
- 4.概念:
- 定义一个池子,在里面放固定数量的进程,有需求就拿一个执行该任务,处理完进程并不会关闭,而是将这个进程放回进程池中,继续等待任务
- 5. 方法:
- p.apply() 每个任务是排队进行,类似于串行失去意义
- p.apply_async()异步非阻塞,不用等待当前进程执行,随时根据系统调度来进行进程切换,若用异步提交任务,等进程池中的任务执行完需用get收集结果
- p.close() 关闭进程
- p.join() 主进程阻塞,等待所有工作进程退出,只能在close() 后使用
#进程池
from multiprocessing import Process,Pool
import time,os
def run(i):
print(f'{os.getpid()}进程:',i)
time.sleep(0.2)
return i**2
if __name__ == '__main__':
#定义进程池
p=Pool(3)#进程数量为3
#进程列表
p_li=[]
#执行任务 6个任务
for i in range(6):
#p.apply_asunc(目标任务,传递的值) 异步运行 根据进程池中的进程数量,每次最多有3个进程异步执行
#异步执行,执行完一个就释放一个进程,这个进程就去接下一个任务,不用等待,不管其他进程状态
res=p.apply_async(run,args=(i,))
p_li.append(res)
#关闭进程池
p.close()
#等待所有子进程执行完
p.join()
#使用get收集apply_async的结果
for i in p_li:
print(f'{os.getpid()}执行结果为',i.get())
#结果:
5180进程: 0
19428进程: 1
19488进程: 2
5180进程: 3
19428进程: 4
19488进程: 5
1844执行结果为 0
1844执行结果为 1
1844执行结果为 4
1844执行结果为 9
1844执行结果为 16
1844执行结果为 25
Pool的执行流程:有三个阶段:
1. 一个进程池接受很多任务,然后分开执行任务,
2. 不再接受任务了
3. 等所有任务完成,回到进程池
close() 停止接收新任务,若还有任务就会抛出异常
join() 等待所有任务完成,必须在close() 后调用 否则会抛出异常
terminate 非正常终止,内存不够用时,垃圾回收调用此方法
2、线程
-
概念:
- 线程是应用程序中工作的最小单元,又称为微进程
- 是cpu调度的基本单位,每个进程至少有一个线程,这个线程通常就是主线程
- 程序启动会有一个主线程,自己创建的线程称为子线程,多线程可以完成多任务
- 一个进程默认有一个线程,进程里可以创建多个线程,线程是依附在进程里面的
- 线程里面是资源共享的
-
优点:共享内存,IO操作时,创造并发操作
-
多线程类似于同时执行多个不同的程序,多线程运行有如下优点:
- 使用线程可以将占据长时间的程序任务放到后台去处理
- 程序的运行速度可能加快
- 在一些等待任务上实现用户输入,文件读写,网络收发数据等,线程就比较有用了,在这种情况下我们可以释放一些珍贵的资源如内存占用
- . 线程在执行过程中与进程还是有区别的,每个独立的线程有一个程序运行入口,顺序执行序列和程序的出口。但线程不能独立执行,必须依存在进程
- . 每个线程都有自己的一组cpu寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的cpu寄存器的状态
- . 指令指针和堆栈指针寄存器是线程上下文中的两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存
线程可以被抢占(中断)
在其他线程正在运行时,线程可以暂缓搁置(也称睡眠)—这就是线程的退让
- 线程可分为:
- 内核线程:由操作系统内核创建和撤销
- 用户线程:不需要内核的支持而在用户程序中实现的线程。
- Python中常用的线程模块
- _thread
- threading(推荐使用)
- thread 模块已废弃,可以用threading代替,
- Python中使用线程的方式:
- 函数或或者用类来包装线程对象
- threading 中提供的方法:
- threading.currentThread(): 返回当前线程的变量
- threading.enumerate(): 返回一个正在运行的线程的list(不包括启动前和终止后的线程)
- threading.activeCount(): 返回正在运行的线程数量,与len(threading.enurmerate())有同效
- run(): 用以表示线程活动的方法
- start(): 启动线程活动
- join(time): 阻塞线程至线程中止,time可选参数
- setDaemon(True):守护主线程,跟随主线程退(必须放在start()上方)
- isAlive(): 返回线程是否活动
- getName(): 返回线程名
- setName():设置线程名
- 创建线程的方式:
- 通过模块方式
#单线程
import time
def fn1():
print('这是单线程1')
time.sleep(1)
print('这是单线程任务1')
def fn2():
print('这是单线程2')
time.sleep(0.5)
print('这是单线程任务2')
if __name__ == '__main__':
fn1() #这是单线程1 这是单线程任务1
fn2() #这是单线程2 这是单线程任务2
#多线程
import time
#导入线程模块
import threading
def fa(a):
print('这是线程1','参数为',a)
print('当前线程的名字为:',threading.current_thread().name)
time.sleep(1)
print('这是任务1')
def fb():
print('这是线程2')
time.sleep(0.2)
print('这是任务2')
if __name__ == '__main__':
#创建线程对象
t1=threading.Thread(target=fa,args=('多线程传参',))
t2=threading.Thread(target=fb)
#修改子线程的名字
t1.setName('python')
#获取子线程的名字
print('线程一的名字',t1.getName())
#守护线程 主线程结束子线程跟着结束
t1.setDaemon(True)
t2.setDaemon(True)
#启动子线程
t1.start()
t2.start()
#阻塞线程join()
t1.join()
t2.join()
print('这是主线程')
#获取获取当前线程的名字
print('当前线程的名字:',threading.current_thread().name)# 当前线程的名字: MainThread
结果:
线程一的名字 python
这是线程1 参数为 多线程传参
当前线程的名字为: python
这是线程2
这是任务2
这是任务1
这是主线程
当前线程的名字: MainThread
2. 通过创建类来创建线程(对__init__和run()方法进行重写)
#线程类
import time
from threading import Thread
class Th(Thread):
def __init__(self,n):
self.n=n
threading.Thread.__init__(self)
print('这是线程1的参数',n,'当前线程是:',threading.current_thread().name)
def run(self):
print(f'这是线程{self.n}','当前线程是:',threading.current_thread().name)
time.sleep(0.2)
print(f'这是任务{self.n}')
if __name__ == '__main__':
#创建线程对象
t1=Th(1)
t2=Th(2)
# t1.run()
t1.start()# start() 自动执行run方法
t2.start()
结果:
这是线程1的参数 1 当前线程是: MainThread
这是线程1的参数 2 当前线程是: MainThread
这是线程1 当前线程是: Thread-1
这是线程2 当前线程是: Thread-2
这是任务2这是任务1
- 资源共享
#资源共享
import threading
li=[]
def write():
for i in range(4):
li.append(i)
print(threading.current_thread().name,'写入的数据是',li)
def read():
print(threading.current_thread().name,'读取的数据为',li)
if __name__ == '__main__':
t1=threading.Thread(target=write)
t2=threading.Thread(target=read)
#启动子线程
t1.start()
t1.join()
t2.start()
#资源竞争
a=0
b=1000000
from threading import Thread
def fa():
for i in range(b):
global a
a+=1
print('a的第一次值为',a)
def fb():
for i in range(b):
global a
a+=1
print('a的第二次值为',a)
if __name__ == '__main__':
t1=Thread(target=fa)
t2=Thread(target=fb)
t1.start()# a的第一次值为 1000000
t2.start()# a的第二次值为 1770975
结果:
Thread-1 写入的数据是 [0, 1, 2, 3]
Thread-2 读取的数据为 [0, 1, 2, 3]
- 线程同步
- 同步的方式:线程等待(join())/ 互斥锁
- 同步的概念:线程A写入,线程B读取线程A写入的值。线程A写入后线程B才能读取,因此线程A与线程B是同步关系
- 线程同步:主线程和创建的子线程之间各自执行完自己的代码直到结束
- 互斥锁
- 概念:保证多个线程访问共享数据不会出现报错,保证同一时刻只能有一个线程去操作
- 使用方法:threading模块里面定义了lock这个函数,通过调用这个函数可以获得一把锁
- acquire() 加锁 release() 解锁 两个方法成对出现
- 互斥锁的作用:保证同一时间只有一个线程去操作共享数据,不会出现错误
- 缺点:使用互斥锁 会影响代码的执行效率,多任务改成了单任务。 使用互斥锁没用好会出现死锁
# 线程同步--互斥锁
from threading import Thread,Lock
s = 0
b = 1000000
def fa():
# with lock:
lock.acquire()
for i in range(b):
global s
s += 1
print('这是s',threading.current_thread().name,s)
lock.release()
def fb():
# with lock:
lock.acquire()
for i in range(b):
global s
s+=1
print('这是s',threading.current_thread().name,s)
lock.release()
if __name__ == '__main__':
lock= Lock()
t1=Thread(target=fa)
t2=Thread(target=fb)
t1.start()# 这是s Thread-1 1000000
t2.start()# 是s Thread-2 2000000
3、协程
- 协程介绍
- 对于协程来说,程序员就是神,你想让他执行到哪就执行到哪
- 协程:单线程的并发又称为微线程,纤程
- 协程的优点:
- 没有切换的消耗
- 没有锁的概念
- 协程是python中的另一个实现多任务的方式,只不过比线程更小,占用更小的执行单元(理解为需要的资源)
- 为什么说他是执行单元?因为他自带cpu的上下文,这样只要在合适的时机,可以把一个协程切换到另一个协程。只要这个协程中保存或恢复cpu上下文 那么程序是还可以运行的
- 协程使用场景:如果一个线程里的io操作(网络等待,文件操作等),比较多的话,协程就比较合适
- 协程适用于高并发处理
- greenlet
- 用C实现的协程模块
- 为了更好的使用协程完成多任务,python中的greenlet模块对其封装,从而使得任务变得更加简单
- 通过switch来实现任意的函数间的切换,属于手动操作,当遇到io操作时,程序会阻塞,不能进行自动切换
- gevent
- 遇到io操作,会自动切换,属于主动切换
- 在gevent中用到的主要模式是greenlet
- gevent,sowan(函数名) 创建协程对象
- join(): 阻塞,等待某个协程执行完毕
- joinall():参数是一个协程对象列表,会等待所有的协程都执行完毕在退出
- gevent 中自带sleep耗时函数,若没有遇到能识别的io操作,不会执行多任务切换,实现并发效果
- 程序补丁:猴子补丁功能,在模块运行时切换的功能
- 总结:
- 进程是资源分配的基本单位,线程是cpu调度的基本单位
- 对比:
- 进程:切换需要的资源最大,效率低
- 线程:切换需要的资源一般,效率一般
- 协程:切换需要的资源很小,效率很高
- 多线程:适合IO密集型操作(读写 如爬虫)
- 多进程:适合cpu密集型操作(科学计算,对视频高清解码)
#协程简单实现
def A():
while True:
yield '人要有精神'
def B():
while True:
yield '诸君,且听龙吟'
if __name__ == '__main__':
a=A()
b=B()
while True:
print(next(a))
print(next(b))
诸君,且听龙吟
人要有精神
诸君,且听龙吟
#使用greenlet实现协程
from greenlet import greenlet#导入模块
def A():
print('开始吃饭')
b.switch()#切换到b中运行
print('吃饱了!')
def B():
print('开始睡觉')
a.switch()#切换到A中运行
print('睡醒了!')
if __name__ == '__main__':
#创建协程对象
a=greenlet(A)
b=greenlet(B)
a.switch()
b.switch()
开始吃饭
开始睡觉
吃饱了!
睡醒了!
#使用gevent
import gevent
def A():
print('开始睡觉')
gevent.sleep(0.4)
print('睡醒了!')
def B():
print('开始学习')
gevent.sleep(0.2)
print('学协程')
if __name__ == '__main__':
# 创建
a=gevent.spawn(A)
b=gevent.spawn(B)
a.join()#运行
#当执行A/B 两个任务,当A、B遇到耗时任务时,gevent会让A继续执行。同时B也开始执行 当A完成耗时操作后,B对应的时间也完成了耗时任务
开始睡觉
开始学习
学协程
睡醒了!
#使用joinall
#给程序打补丁--猴子补丁
from gevent import monkey
import gevent,time
monkey.patch_all()#此处可以将time换成gevent能识别的睡眠 补丁需放在被补程序前方
def A(name):
for i in range(4):
# gevent.sleep(0.2)
time.sleep(0.2)
print(name,i)
gevent.joinall([# 等待执行完里面所有任务
gevent.spawn(A,'协程1'),
gevent.spawn(A,'协程2')
])
协程1 0
协程2 0
协程1 1
协程2 1
协程1 2
协程2 2
协程1 3
协程2 3
- 花开成海,思念成灾。你再不来,我便要老去了