Day12 PythonWeb全栈课程课堂内容

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()
  • 程序运行过程:
    1. 运行task函数,输出print('--1--'),延时time.sleep(0.1)之后,yield暂停等待。
    2. 运行task2函数,输出print('--2--'),延时time.sleep(0.1)之后,yield暂停等待。
    3. 运行task函数,输出print('--1--'),延时time.sleep(0.1)之后,yield暂停等待。
    4. 运行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. 线程与进程,协程对比

image.png

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密集型, 适合多线程
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值