时间片轮转
每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间
进程切换(process switch) - 有时称为上下文切换(context switch)
上下文管理器(contextor)
简洁版:
为了优雅简洁:
具体工作:上下文管理器的任务是: 代码块执行前准备,代码块执行后收拾。
with contextor [as var]:
with_body
实现方式:
上下文管理协议: 实现了 __enter__ 和 __exit__
必须实现两个方法: 一个负责进入语句块的准备操作, __enter__
另一个负责离开语句块的善后操作。 __exit__
__enter__ 方法将在进入代码块前被调用。
__exit__ 方法则在离开代码块之后被调用(即使在代码块中遇到了异常)
__exit__:
__exit__函数返回True, 终止程序
__exit__函数返回None或者False,异常会被主动raise
为了优雅简洁:
比如文件的, 打开(open)和关闭(close)这样的代码就得copy很多遍,显然不是一个优雅的设计
具体工作:上下文管理器的任务是: 代码块执行前准备,代码块执行后收拾。
with contextor [as var]:
with_body
实现方式:
上下文管理协议: 实现了 __enter__ 和 __exit__
必须实现两个方法: 一个负责进入语句块的准备操作, __enter__
另一个负责离开语句块的善后操作。 __exit__
__enter__ 方法将在进入代码块前被调用。
__exit__ 方法则在离开代码块之后被调用(即使在代码块中遇到了异常)
如果语句块内部发生了异常,__exit__方法将被调用,而异常将会被重新抛出(re-raised)。
可以让__exit__方法简单的返回True,来忽略语句块中发生的所有异常(大部分情况下这都不是明智之举)。
class Contextor:
def __enter__(self):
pass
def __exit__(self, exc_type, exc_val, exc_tb):
pass
contextor = Contextor()
with contextor [as var]:
with_body
with as 语句:
1.with语句使用的时候,上下文管理器会自动调用__enter__方法,然后进入运行时上下文环境,
如果有as 从句,返回自身或另一个与运行时上下文相关的对象,值赋值给var。
2. 当with_body执行完毕退出with语句块或者with_body代码块出现异常,
则会自动执行__exit__方法,并且会把对于的异常参数传递进来。
__exit__函数返回True, 终止程序
__exit__函数返回None或者False,异常会被主动raise
执行 contextor 以获取上下文管理器
加载上下文管理器的 exit() 方法以备稍后调用
调用上下文管理器的 enter() 方法
如果有 as var 从句,则将 enter() 方法的返回值赋给 var
执行子代码块 with_body
调用上下文管理器的 exit() 方法,如果 with_body 的退出是由异常引发的,那么该异常的 type、value 和 traceback 会作为参数传给 exit(),否则传三个 None
如果 with_body 的退出由异常引发,并且 exit() 的返回值等于 False,那么这个异常将被重新引发一次;如果 exit() 的返回值等于 True,那么这个异常就被无视掉,继续执行后面的代码
并行/并发
并行(parallel): 多个CPU同时执行 (parallel: 两个进程在两条平行线上跑)
物理上的同时发生, 真的多任务
---> 三个人同时吃三个馒头
具体细节: 同一时刻, 有多条指令在 多个处理器 上同时执行
并发(concurrency): 一个挨着一个执行 (current : 流动, con + currency 共同去流动)
逻辑上的同时发生, 假的多任务
---> 一个人同时吃三个馒头
具体细节: 同一时刻 只能有一条指令被执行, 但是多个进程(线程)指令 被快速的 轮换执行,
把时间分成若干段, 使多个进程(线程)快速交替的执行.
进程
CPU的执行过程: 所有任务都是 一个一个的轮流执行的,
先加载程序A的上下文,然后开始执行A, 保存程序A的上下文.
调用下一个要执行的程序B的上下文,然后开始执行B, 保存程序B的上下文
进程: 包括 上下文切换 的程序,执行时间总和
CPU加载上下文 + CPU执行 + CPU保存上下文
程序的的组成: 多个分支 和多个程序段
程序A分成: a, b, c等多块组成
程序A的执行:
1.加载程序A的上下文
2.执行A的a小段, 执行A的b小段, 执行A的c小段
3. CPU保存A的上下文
线程: a,b,c 就是线程.
特点: 线程共享了进程的上下文环境, 是更小的时间段
进程和线程都是时间段, 是CPU工作的时间段,
进程 是一个程序的时间段, -->管理资源 的基本单位
线程 是程序内,一小段程序块 的时间段 --> CPU调度 的基本单位
线程thread
threading module
主线程最后结束:所有子线程都结束了,主线程才结束 ---> 因为如果主线程先死,会导致它的所有子线程一起死
何时创建子线程: 调用start()时
当调用Thread的时候,不会创建线程(准备工作)
当调用Thread创建对象的start()方法时,
才会创建线程(真的创建),
并运行这个线程
线程结束: 函数结束
target指明去哪里调用函数
join([timeout]) 在调用的地方等待,直到子线程完成操作后,才可以接着往下执行(线程虽然默认等待, 单是为了保证,还是需要写一下)
setDaemon(True) 默认是False,setDaemon 是把主线程变成守护线程
主线程结束之后立马就会把所有子线程kill掉
用途:一般是用来当做某种服务的。如果这服务现在要停止了,那么是不应该等待其子线程的。
daemon : /'di:men'/ 守护神;守护进程
python中,主线程默认等待子线程结束(其它语言需调用join)
t_1.join() 在调用的地方等待,直到子线程完成操作后,才可以接着往下执行
t_2.join()
方法一: fuction
1.定义函数: def function_name():
2.创建子线程: t_1 = threading.thread(target = fuction_name)
3.启动子线程: t_1.start()
方法二: object
1.定义一个继承Thread为父类的 类,比如:MyThread
2.定义一个run()方法
3.使用 这个类创建实例对象 t = MyThread()
4.调用对象的start() 方法 t.start()
全局变量
python的全局变量不是 程序级别的(全局唯一), 而是模块级别的.
在一个模块中,即一个python文件中,全局变量才成立
共享全局变量
在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据
资源竞争:缺点就是,线程是对全局变量随意遂改可能造成多线程之间对全局变量的混乱(即线程非安全)
后果是: 每个线程的任务都没有做完,就被CPU给请出去了,导致最后结果不正确
一个程序就像是一个房子
单任务时: 家里面就只有你一个人,你一个使用厨房,厕所,电视
多任务时: 就是你结了婚,家里还有一另一半,你们就需要共同使用厨房,厕所,电视
资源竞争: 你和老婆同时想看电视,可电视只有一台,就会造成竞争,最后结果时谁都看的不开心
原子性: 要么不做, 要么做绝
互斥锁
互斥锁为资源引入一个状态:锁定/非锁定
保证了 每次只有一个线程 进行写入操作,从而保证了多线程情况下数据的正确性。
优点: 确保了某段关键代码只能由一个线程从头到尾完整地执行
# 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。
# 释放
mutex.release()
直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。
缺点:
阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,
效率就大大地下降了
由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,
可能会造成死锁
python进程
multiprocessing module
程序:例如xxx.py这是程序,是一个静态的
进程:一个程序运行起来后,代码+用到的资源 称之为进程,它是操作系统分配资源的基本单元。
代码只有一份,因为不改变,所以不必要浪费内存资源.
不一样的是各自拥有的一些数据,这是各自会改变的东西(写时拷贝)
写时拷贝: 修改的时候才去拷贝(一旦共享的数据发生改变,另一进程就拷贝一份自己独有的,目的是为了不对其它进程造成影响)
创建一个Process实例,用start()方法启动
Process(self, group=None, target=None, name=None, args=(), kwargs={})
target:如果传递了函数的引用,可以任务这个子进程就执行这里的代码
args:给target指定的函数传递的参数,以元组的方式传递
kwargs:给target指定的函数传递命名参数
name:给进程设定一个名字,可以不设定
group:指定进程组,大多数情况下用不到
Process创建的实例对象的常用方法:
start():启动子进程实例(创建子进程)
is_alive():判断进程子进程是否还在活着
join([timeout]):是否等待子进程执行结束,或等待多少秒
terminate():不管任务是否完成,立即终止子进程
执行顺序: 主进程与子进程之间没有先后顺序, 后系统 调度算法 决定
死亡: 主进程默认子进程死亡以后才会才会结束(收尸),
但主进程结束不影响子进程运行
主进程先死, 子进程 -->孤儿进程
子进程先死, 父进程不收尸( join) -->僵尸进程
进程间不同享全局变量
进程pid:
import os
os.getpid()
三种进程
- 守护进程 :
- 在后台运行,不与任何终端关联的进程,
- 通常情况下守护进程在系统启动时就在运行,它们以root用户或者其他特殊用户(apache和postfix)运行,并能处理一些系统级的任务.
- 习惯上守护进程的名字通常以d结尾(sshd),但这些不是必须的.
- 孤儿进程:
- 如果父进程先退出,子进程还没退出那么子进程将被 托孤给init进程,
- 这是子进程的父进程就是init进程(1号进程). 现在已经改变
- 僵尸进程:
- 有一种状态是僵死状态,就是进程终止后进入僵死状态(zombie),等待告知父进程自己终止,后才能完全消失.
- 如果一个进程已经终止了,但是其父进程还没有获取其状态,那么这个进程就称之为僵尸进程.
- 僵尸进程还会消耗一定的系统资源,并且还保留一些概要信息供父进程查询子进程的状态可以提供父进程想要的信息.一旦父进程得到想要的信息,僵尸进程就会结束.
进程与线程的区别
一个程序至少有一个进程,一个进程至少有一个线程
线程划分尺度小于进程,并发性高
进程拥有独立内存单元,线程共享内存
线程不能独立执行,必须依赖进程
进程: 资源 管理
线程: cpu 调度
进程
pool(进程池)
解决同一进程的重复利用问题:
同一进程的重复利用: 使用同一个进程,处理不同的问题
创建进程池:
po = multiprocessing.Pool(num) num:池内最大进程数
进程加入进程池:
po.apply_async(要调用的目标,(目标的参数元组,))
关闭进程池的入口:
po.close()
通过pool创建的子进程, 主进程不会等!(不是不想等,而是它控制不了)
需要手动添加: join() --> 内部也是调用 各自进程的join()方法, 相当于做了一个集成
po.join()
可迭代对象(iterable)
存在意义: 节省大量的存储(内存)空间
是可以返回一个 迭代器的对象 都可称之为可迭代对象
x = [1, 2, 3] for elem in x: ...
可迭代对象实现了
__iter__
方法,__iter__
的作用就是返回一个该对象的 迭代器具体过程:
获取可迭代对象的迭代器
利用该迭代器对该对象进行迭代操作
接收可迭代对象
- for, list, tuple 等
迭代器(iterator)
迭代
- 什么是迭代
- 重复,往下
- 在之前的基础之上,出现新的版本 的过程
- 迭代和递归的区别
- 迭代: 重复同一个动作
- 递归: 自己调用自己 –>在自己的基础之上, 再调用自己
- 递归是迭代的特例 : 重复调用自己
作用: 某对象的迭代器, 就是用来 对该对象执行迭代操作
一个list的迭代器, 就是用来对 这个list 进行迭代操作
用A的迭代器, 对A进行迭代操作
每个迭代器都是独立的,对一个对象执行iter(),可以N次执行, 获得N个迭代器, 重点: 每一个迭代器都是相互独立的个体
错误方式
任何实现了
__iter__
和__next__()
方法的对象__iter__
返回迭代器自身 (self)__next__
返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration
异常
判断是否为迭代器
- collection 模块 , 中 isinstance( )方法
- collection.isinstance([], Iterator) –> 判断是否为迭代器
- collection.isinstance([], Iterable) –> 判断是否可以迭代
iter(): 获取这些可迭代对象的迭代器 调用了可迭代对象的__iter__方法 next(): 对获取到的迭代器不断使用next()函数来获取下一条数据 调用了迭代器的__next__方法 迭代完最后一个数据之后,再次调用next()函数会抛出StopIteration的异常
迭代器协议
迭代器协议是指:对象需要提供next方法,它要么返回迭代中的下一项,要么就引起一个
StopIteration
异常,以终止迭代可迭代对象就是:实现了迭代器协议的对象
Python中,文件还可以使用for循环进行遍历呢?
这是因为,在Python中,文件对象实现了迭代器协议,for循环并不知道它遍历的是一个文件对象,它只管使用迭代器协议访问对象即可。
协议: 是一种约定,可迭代对象实现迭代器协议,Python的内置工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象。
生成器
Python使用生成器对 延迟操作 提供了支持。
所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。这也是生成器的主要好处。
两种不同的方式提供生成器:
生成器函数:常规函数定义,但使用yield语句而不是return语句返回结果。
yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,
以便下次重它离开的地方继续执行
生成器表达式:类似于列表推导,但是生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
生成器
简洁版:
作用: 暂停执行, 并返回一个值
不保存整体数据, 而是保存生成这个数据的 方法/方式
生成器函数:
创建: 把 return 换成 yeild 来返回一个值,就是一个生成器
启动 并 取值:
next() : 不需要传参数
send(args) :可以在启动时,传入一个参数
是一个特殊的迭代器:
底层仍然有: __iter__, __next__ 方法
生成器函数
语法上和函数类似: 生成器使用yield语句返回一个值,而常规函数使用return语句返回一个值
自动实现迭代器协议:对于生成器,Python会自动实现迭代器协议
使用next()取值:我们可以调用它的next方法
- 在没有值可以返回的时候,生成器自动产生StopIteration异常
使用send(args)取值, 在启动生成器时,会传入一个参数
- send(): 启动时可以传任意参数
- next(): 实际上,它也传参数, 只是它传的是 None
状态挂起:生成器使用yield语句返回一个值。
yield语句挂起该生成器函数的状态,以便之后从它离开的地方继续执行
好处:1.生成器的好处是延迟计算,一次返回一个结果。
2.使用生成器以后,代码行数更少。
唯一注意事项:生成器只能遍历一次
如果生成器里面有 return 返回值, 在异常的value属性接收
生成器表达式(generator expression)
- 把列表推导式的 中括号[ ] 换成 小括号( )
else
在while循环
while True: .... else: .... 这个else语句是在while语句正常结束的时候执行的 else起到的作用是and
在for循环
try: .... except: .... else: .... finally: .... 如果try中代码正常执行,才会执行else
携程
携程: 就是通过人工暂停 模仿CPU的时间片轮转 –> yield
使切换任务像切换函数一样简单
比如有两个函数A,B ,通过yield ,实现: A 执行一段时间,暂停; B执行一段时间,暂停;
相互切换; 当切换足够快时, 就相当于是同时执行, 完成了多任务(很像是 并发)
gevent最大特点: 遇到延时它就切换; 没遇到延时它就不切换.
携程的最大特点: 把延时操作的等待时间, 利用起来!
- 把一个线程在等待某个资源的时间, 拿去做其它函数的代码
- 利用一个函数等待的时间,去做其它函数
is_A原则和has_A原则
is_A 继承关系 :
has_A是组合关系: 描述一个类中有另一个类型的实例