1. 协程
-
介绍
- 协程,又称为微线程,它是实现多任务的另一种方式,只不过是比线程更小的执行单元。因为它自带CPU的上下文,这样只要在合适的时机,我们就可以把一个协程切换到另一个协程。(使用到延迟的时间)
-
CPU上下文(CPU寄存器和程序计数器):
- CPU寄存器是CPU的内置的容量小,但速度极快的内存。
- 程序计数器则是用来存储CPU正在执行的指令位置、或者即将执行的下一条指令位置。
-
协程与线程差异
- 线程:每个线程都有自己缓存Cache等等数据,操作系统还会做这些数据的恢复操作。所以线程的切换非常消耗性能。(保存+切换)
- 协程:单纯的操作CPU的上下文,所以一秒切换上百万次系统都能抗住。所以完成多任务的效率比线程和进程都高。
2. yield实现线程
import time
def task():
while True:
print("--1--")
time.sleep(0.1)
yield
def task1():
while True:
print("--2--")
time.sleep(0.1)
yield
def main():
t1 = task()
t2 = task1()
while True:
next(t1)
next(t2)
if __name__ == "__main__":
main()
- 程序运行过程:
- 运行
task
函数,输出print('--1--')
,延时time.sleep(0.1)
之后,yield
暂停等待。 - 运行
task2
函数,输出print('--2--')
,延时time.sleep(0.1)
之后,yield
暂停等待。 - 运行
task
函数,输出print('--1--')
,延时time.sleep(0.1)
之后,yield
暂停等待。 - 运行
task2
函数,输出print('--2--')
,延时time.sleep(0.1)
之后,yield
暂停等待。 - …
- 运行
3. 生成器的扩展
import time
def task():
while True:
print("--1--")
time.sleep(0.1)
yield
t1 = task()
# 如果已经启动生成器,下面的send方法可以填入任何数字。
# 如果没有next启动,而是用send启动第一次必须发送None
# 启动生成器
t1.send(None)
t1.send(2) # 括号内数字为yield返回值。
send
传递参数可以用于循环内的判断退出
import time
def task():
while True:
print("--1--")
time.sleep(0.1)
res = yield
if res:
break
t1 = task()
t1.send(None)
t1.send(None)
输出结果:
--1--
--1--
yield
后面添加参数,表示返回值
import time
def task():
while True:
print("--1--")
time.sleep(0.1)
res = yield 3
if res == 2:
break
t1 = task()
result1 = t1.send(None)
print(result1)
result2 = t1.send(None)
print(result2)
输出结果:
--1--
3
--1--
3
4. greenlet实现协程
from greenlet import greenlet
import time
def task():
while True:
print('task')
g2.switch()
time.sleep(0.5)
def task1():
while True:
print('task1')
g1.switch()
time.sleep(0.5)
if __name__ == '__main__':
g1 = greenlet(task)
g2 = greenlet(task1)
g1.switch() # 缺点手动启动
5. grevent实现协程
import gevent
def task1(n):
for i in range(n):
print(gevent.getcurrent(), i)
gevent.sleep(0.5)
def task2(n):
for i in range(n):
print(gevent.getcurrent(), i)
gevent.sleep(0.5)
# 遇到延迟才切换,如果没有则不切换。
g1 = gevent.spawn(task1, 5)
g2 = gevent.spawn(task2, 5)
g1.join()
g2.join()
<Greenlet at 0x5931c48: task1(5)> 0
<Greenlet at 0x5931d48: task2(5)> 0
<Greenlet at 0x5931c48: task1(5)> 1
<Greenlet at 0x5931d48: task2(5)> 1
<Greenlet at 0x5931c48: task1(5)> 2
<Greenlet at 0x5931d48: task2(5)> 2
<Greenlet at 0x5931c48: task1(5)> 3
<Greenlet at 0x5931d48: task2(5)> 3
<Greenlet at 0x5931c48: task1(5)> 4
<Greenlet at 0x5931d48: task2(5)> 4
- 相对来说创建多个协程时会十分麻烦。
- 不使用
gevent.time()
延时,而是用time.sleep()
延时,则需要使用gevent
内的·monkey
模块。
import gevent
from gevent import monkey
import time
monkey.patch_all()
def task1(n):
for i in range(n):
print(gevent.getcurrent(), i)
time.sleep(0.5)
def task2(n):
for i in range(n):
print(gevent.getcurrent(), i)
time.sleep(0.5)
g1 = gevent.spawn(task1, 5)
g2 = gevent.spawn(task2, 5)
g1.join()
g2.join()
<Greenlet at 0x5181e48: task1(5)> 0
<Greenlet at 0x5181d48: task2(5)> 0
<Greenlet at 0x5181e48: task1(5)> 1
<Greenlet at 0x5181d48: task2(5)> 1
<Greenlet at 0x5181e48: task1(5)> 2
<Greenlet at 0x5181d48: task2(5)> 2
<Greenlet at 0x5181e48: task1(5)> 3
<Greenlet at 0x5181d48: task2(5)> 3
<Greenlet at 0x5181e48: task1(5)> 4
<Greenlet at 0x5181d48: task2(5)> 4
- 解决多个协程
import gevent
from gevent import monkey
import time
monkey.patch_all()
def task1(n):
for i in range(n):
print(gevent.getcurrent(), i)
time.sleep(0.5)
def task2(n):
for i in range(n):
print(gevent.getcurrent(), i)
time.sleep(0.5)
gevent.joinall([
gevent.spawn(task1, 5),
gevent.spawn(task2, 5),
])
<Greenlet at 0x68f2148: task1(5)> 0
<Greenlet at 0x68f2248: task2(5)> 0
<Greenlet at 0x68f2148: task1(5)> 1
<Greenlet at 0x68f2248: task2(5)> 1
<Greenlet at 0x68f2148: task1(5)> 2
<Greenlet at 0x68f2248: task2(5)> 2
<Greenlet at 0x68f2148: task1(5)> 3
<Greenlet at 0x68f2248: task2(5)> 3
<Greenlet at 0x68f2148: task1(5)> 4
<Greenlet at 0x68f2248: task2(5)> 4
6. 爬虫案例
import gevent
from gevent import monkey
monkey.patch_all()
import requests
def download(url):
print('The Url %s' % url)
res = requests.get(url)
data = res.text
print(len(data))
gevent.joinall([
gevent.spawn(download, 'https://www.baidu.com/'),
gevent.spawn(download, 'https://www.zhihu.com/'),
gevent.spawn(download, 'https://www.jianshu.com/')
])
# download('https://www.baidu.com/')
# download('https://www.zhihu.com/')
# download('https://www.jianshu.com/')
- 用协程方式比常规方式节约许多时间
7. 线程与进程,协程对比
8. 简单总结
- 进程是资源分配的单位
- 线程是操作系统调度的单位
- 进程切换需要的资源很最大,效率很低
- 线程切换需要的资源一般,效率一般
- 协程切换任务资源很小,效率高
- 多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中所以是并发。
9. 课堂问题
-
如何区别可变数据类型和不可变数据类型
-
从对象内存地址
- 可变数据类型,在内存地址不变的情况下,值可变。
- 不可变数据类型,在内存地址改变,值也跟着改变。
-
可变数据类型:列表、字典、集合
-
不可变数据类型:字符串、元组、整数、布尔
-
-
Python 垃圾回收机制?
- 引用计数
- 引用计数为0的时候,会被回收。
- sys.getrefcount()
- 标记清除
- 分代回收
- 引用计数
-
Python 中会有函数或成员变量包含单下划线前缀和结尾,和双下划线前缀结尾,区别是什么?
- 保护变量:不希望被类外部访问,希望被类对象和子类对象去访问
- 私有成员:类对象可以访问,子类是不能访问的。
-
如何判断一个对象是函数还是方法?(面试)
from types import MethodType, FunctionType from inspect import isfunction, ismethod class B: def f(self): pass def task(): pass print(isfunction(task)) # True print(isinstance(task, FunctionType)) # True print(isinstance(task, MethodType)) # False
-
super函数的用法
- 调用父类中的方法
class A: def __init__(self): print('A') def task(self): print('task') class B(A): def __init__(self): print('B') super(B, self).__init__() super(B, self).task() b = B()
-
使用isinstance和type的区别
- isinstance和type都是查看对象类型
a = 1 type(a) # int 不考虑继承关系 isinstance(a, int) # True 考虑继承关系
-
创建大量实例节省内存
- 关闭动态绑定属性
__slots__ = ()
-
上下文管理器
with __enter__ # 获取资源 __exit__ # 释放资源
-
判断一个对象中是否具有某个属性
class S: name = 'Sam' def task(self): print("task") a = S() print(hasattr(a, 'name')) print(hasattr(a, 'names')) print(hasattr(a, 'task')) print(hasattr(a, 'task2'))
-
property动态属性的使用
- 将方法变成属性调用
class C(object): def getx(self): return self._x def setx(self, value): self._x = value def delx(self): del self._x x = property(getx, setx, delx, "I'm the 'x' property.") class C(object): @property def x(self): "I am the 'x' property." return self._x @x.setter def x(self, value): self._x = value @x.deleter def x(self): del self._x
-
如何使用type创建自定义类
type(name, bases, dict) -> a new type
- name: 类名
- bases:继承的父类
- dict:属性
type{'User', (BaseClass,), (类方法, 静态方法, 魔术方法)
# 案例 user = type('User', (), {'name':"Sam"}) print(dir(user))
- 生成器的创建
yield
- ()
- send方法启动生成器
send(None) 第一次
next()
send(随便写)
- TCP与UDP的区别
- TCP 面向连接 UDP 是无连接的
- TCP 是可靠服务 UDP 尽量交付
- UDP 实时性较好
- TCP 是点对点的通信
- TCP 要求资源比较多, UDP资源比较少
- TCP的服务端通信流程
- 创建套接字
- 绑定
bind
IP和PORT - lishen 主动变被动
- accept
- recv/send 接收/发送
- 关闭套接字
- 创建线程的两种方式
import threading
t1 = threading.Thread(target=任务名, args=(参数,))
t2 = threading.Thread(target=任务名, args=(参数,))
- 继承
class A(threading,Tread):
def run(self):
pass
- 解释资源竞争,以及解决方案
- 原因:一个线程不能保证,当前的所有步骤能全部执行完,去切换切换到另外一个线程。
- 解决方案:加互斥锁
- 死锁出现的原因
- 线程A acquire(a) acquire(b)
- 线程B acquire(b) acquire(a)
- 银行家算法解决此方法
- 进程之间的通信,以及进程池中的进程通信
- multiprocessing.Queue 进程之间的通信
- multiprocessing.Manager().Queue 进程池中的进行通信
- 同步,异步,阻塞,非阻塞
并行:真的多任务, 任务数量小于CPU数量。
并发:假的多任务, 任务数量大于CPU数量。
同步:调用IO操作,必须等待IO操作完成才返回调用结果,有先后顺序。
异步:调用IO操作,不必等待IO操作完成返回调用结果,没有先后顺序。
阻塞:代码被卡住,不能继续向下运行,需要等待。
非阻塞:代码没有被卡住,可以继续向下运行。
- 进程 、线程、协程对比
进程是资源分配的单位
线程是操作系统调度的单位
进程切换需要的资源很最大,效率很低
线程切换需要的资源一般,效率一般
协程切换任务资源很小,效率高
多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中 所以是并发
- 描述Python GIL的概念, 以及它对Python多线程的影响
- Python语言和GIL没有关系,CPython解释器中的历史遗留问题
- GIL全局解释器,用来阻止同一个进程下的多个线程同事执行
- 计算密集型, 不适合多线程,适合多进程 因为没有延迟
- IO密集型, 适合多线程