python线程池+协程

线程池

Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。

但是对于IO密集型的任务,多线程还是起到很大效率提升,这是协同式多任务
当一项任务比如网络 I/O启动,而在长的或不确定的时间,没有运行任何 Python 代码的需要,一个线程便会让出GIL,从而其他线程可以获取 GIL 而运行 Python。这种礼貌行为称为协同式多任务处理,它允许并发;多个线程同时等待不同事件。

一个线程的运行时间可以分为3部分:线程的启动时间、线程体的运行时间和线程的销毁时间。在多线程处理的情景中,如果线程不能被重用,就意味着每次创建都需要经过启动、销毁和运行3个过程。这必然会增加系统相应的时间,降低了效率。

对于任务数量不断增加的程序,每有一个任务就生成一个线程,最终会导致线程数量的失控,例如,整站爬虫,假设初始只有一个链接a,那么,这个时候只启动一个线程,运行之后,得到这个链接对应页面上的b,c,d,,,等等新的链接,作为新任务,这个时候,就要为这些新的链接生成新的线程,线程数量暴涨。在之后的运行中,线程数量还会不停的增加,完全无法控制。所以,对于任务数量不端增加的程序,固定线程数量的线程池是必要的。

python3中自带了concurrent.futures模块,可以实现线程池

from concurrent.futures import ThreadPoolExecutor
import time
import random
from concurrent.futures import ThreadPoolExecutor
import time

def hello(num):
    time.sleep(random.randint(1, 3))
    print("{}只小鸭子,咿呀咿呀呦".format(num))

if __name__ == "__main__":
    excutor1 = ThreadPoolExecutor(max_workers=3)
    for i in range(1,8):
        future = excutor1.submit(hello,i)
        future.done()
    with ThreadPoolExecutor(3) as executor1:
    executor1.map(hello, [1,2,3])

在提交任务的时候,有两种方式,一种是submit()函数,另一种是map()函数,两者的主要区别在于:

  • map可以保证输出的顺序, submit输出的顺序是乱的
  • 如果你要提交的任务的函数是一样的,就可以简化成map。但是假如提交的任务函数是不一样的,或者执行的过程之可能出现异常(使用map执行过程中发现问题会直接抛出错误)就要用到submit()
  • submit和map的参数是不同的,submit每次都需要提交一个目标函数和对应的参数,map只需要提交一次目标函数,目标函数的参数放在一个迭代器(列表,字典)里就可以。

协程

协程:又称微线程,纤程,是一种用户态的轻量级线程
线程是系统级别的,他们是由操作系统调度。协程是程序级别的,由程序员根据需求自己调度。我们把一个线程中的一个个函数称为子程序,那么子程序在执行的过程中可以中断去执行别的子程序。别的子程序也可以中断回来继续执行之前的子程序,这就是协程。也就是说同一线程下的一段代码1执行执行着就可以中断,然后去执行另一段代码2,当再次回来执行代码块1的时候,接着从之前中断的地方开始执行

子程序:在所有的语言中都是层级调用,比如A中调用B,B在执行过程中调用C,C执行完返回,B执行完返回,最后是A执行完毕。是通过栈实现的,一个线程就是执行一个子程序,子程序的调用总是有一个入口,一次返回,调用的顺序是明确的

  • 优点:

    • 1、无需线程上下文切换的开销,协程避免了无意义的调度,由此提高了性能,但是,程序员必须自己承担调度的责任,同时协程也失去了标准线程使用多CPU的能力
    • 2、无需原子操作锁定及同步的开销
    • 3、方便切换控制流,简化编程模型
    • 4、高并发+高扩展性+低成本,一个CPU支持上万个协程不是问题
  • 缺点:

    • 1、无法利用多核资源,协程的本质是单个线程,它不能同时将单个CPU的多个核使用上,协程需要和进程配合使用才能运行在多CPU上。但是一般不需要,除非是CPU密集型的应用
    • 2、进行阻塞操作(耗时IO)会阻塞整个程序

1.创建协程

python对协程的支持是通过generator实现的

def demo():
    print('-> 开始')
    x = yield     #yield将接收send过来的值2赋值给x;并返回None
    print('-> 接收', x)  #打印 -> 接收 2
    c = yield (1 + x)  #接收send过来的3,并把3返回
    print("x,c:",x,c)  #打印 x,c: 2 3

sc = demo(1)  #生成协程对象
#next将激活协程执行到第一个yield
print(next(sc))  #获取到的返回值None,打印None
print(sc.send(2)) #打印3
sc.send(3)  #下面没有yield,抛出异常StopIteration
  • yield的作用

    • 如果yield出现在赋值运算符右边,表示yield将接收协程对象发送(sc.send(2))过来的数据赋值给赋值运算符的左边的变量。
    • 如果yield右边有表达式,表示yield将把表达式的返回给调用者
  • next作用激活协程执行到下一个yield,等价于sc.send(None)

  • 协程在运行过程中有四个状态:

    • GEN_CREATE:等待开始执行
    • GEN_RUNNING:解释器正在执行,这个状态一般看不到
    • GEN_SUSPENDED:在yield表达式处暂停
    • GEN_CLOSED:执行结束
from inspect import  getgeneratorstate
print(getgeneratorstate(sc))
sc.send(None)
print(getgeneratorstate(sc))
sc.send(2)
print(getgeneratorstate(sc))
sc.send(3)
print(getgeneratorstate(sc))

2.生产者和消费者

传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。

如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高

import threading
import time


def product(c):
    print(threading.currentThread().name)
    c.send(None)
    for i in range(1,4):
        print("蒸包子:{}".format(i))
        res = c.send(i)
        print("客户反馈:{}".format(res))
        time.sleep(1)
    c.close()
def eat():
    print(threading.currentThread().name)
    res = ''
    while True:
        bz = yield res
        if not bz:
            return
        print("吃包子:{}".format(bz))
        res = "真好吃"

cs = eat()
product(cs)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值