python进程,线程,协程

进程线程

系统知识

计算机系统抽象组成: CPU + 存储器 + IO

  • 资源:
  1. 计算资源:cpu
  2. 存储资源:内存、磁盘等
    在这里插入图片描述
    多任务
    • 你的计算机同一时刻能做几件事?
    • 查看任务管理器
    • 思考:
    • 对于单核的cpu能不能同时执行多任务?
    • 能不能真正实现多任务同时执行?

• cpu时间片(抽象概念)
• 对于单核cpu同一时刻只能有一个任务运行。

  1. 并发:交替执行(某时间段内的处理能力)
  2. 并行:同时执行
    在这里插入图片描述

• 线程:
线程是操作系统最小的调度单位, 是一串指令的集合
• 进程:
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位

进程与线程

• 真正在cpu上运行的是线程
• 线程共享内存空间;进程的内存是独立
• 一个线程只能属于一个进程,而一个进程可以有多个线程,
但至少有一个线程
• 资源分配给进程,同一进程的所有线程共享该进程的所有资
源。进程的资源是独立的
• 同一个进程的线程之间可以直接交流;两个进程想通信,必
须通过一个中间代理来实现
在这里插入图片描述

多任务操作系统工作模式

• 多进程模式
• 多线程模式
• 多进程+多线程模式

进程状态模型

在这里插入图片描述

多进程与多线程

在这里插入图片描述

threading

io等待时间较长适合多线程
线程
• 线程被称为轻量级进程(Lightweight Process,LWP),是cpu调度的基本单位
• 组成:线程ID、当前指令指针(PC)、寄存器集合、堆栈组成
• 在单个程序中同时运行多个线程完成不同的工作,称为多线程。

功能
threading用于提供线程相关的操作,线程是应用程序中工作的最小单元。

threading模块提供的常用类:
• Thread:创建线程
• Lock/RLock:互斥锁

Thread

Thread构造方法
• 构造方法: Thread(group=None, target=None, name=None, args=(), kwargs={})
• group: 线程组,目前还没有实现,库引用中提示必须是None;
• target: 要执行的方法;
• name: 线程名;
• args/kwargs: 要传入方法的参数。

Thread实例方法
• t.name:获取或设置线程的名称
• t.getName()/setName(name): 获取/设置线程名。
• t.is_alive()、t.isAlive():判断线程是否为激活状态。返回线程是否在运行。正在运行指启动后、终
止前。
• t.ident :获取线程的标识符。线程标识符是一个非零整数,只有在调用了start()方法之后该属性才
有效,否则它只返回None
• t.run() :线程被cpu调度后自动执行线程对象的run方法
• t.start(): 线程准备就绪,等待CPU调度,start会自动调用t.run()

Thread实例方法
• t.join([timeout]): 阻塞当前上下文环境的线程,直到调用此方法的线程终止或到达指定的timeout
(可选参数)。
• t.setDaemon(bool): 设置是后台线程(默认前台线程(False))。(在start之前设置)
• 如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论
成功与否,主线程和后台线程均停止
• 如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程
也执行完成后,程序停止
• t.isDaemon:判断是否为后台线程

小案例

多线程抓取网页数据
• 比较与传统方式上,时间消耗

join的使用
对比使用join前后,时间消耗
在这里插入图片描述
setDaemon的使用
• 观察使用setDaemon前后的差异
在这里插入图片描述
如何获取线程的结果
• 定义一个全局变量、写文件、写Web接口… • 自定义线程类
• 自定义线程获取结果

资源总是有限的,程序运行如果对同一个对象进行操作,则有可能造成
一些异常情况,如:
• 数据前后读取不一致
• 资源的争用甚至导致死锁

Lock

在多线程中使用lock可以让多个线程在共享资源的时候遵循一定的规则。
常见锁类型
• Lock()/RLock:普通锁(互斥锁)
解决资源争用,数据读取不一致等
• Semaphore :信号量
最多允许同时N个线程执行内容
• Event: 事件锁
根据状态位,决定是否通过事件
• Condition: 条件锁
Lock()/RLock:普通锁(互斥锁)
• 解决资源争用,数据读取不一致等

构造方法:

Lock()互斥锁

实例方法:
• acquire([timeout]): 尝试获得锁定。使线程进入同步阻塞状态。
• release(): 释放锁。使用前线程必须已获得锁定,否则将抛出异常。

import random
import time
from threading import *

num = 1
lock = Lock()
def task():
    name = current_thread().name
    print(f"{name}start")
    time.sleep(1)
    print(f"{name}需要锁了")
    global num
    
    #加锁
    lock.acquire()
    num += 1
    print(f"{name}curent num is => {num}")
    time.sleep(random.random())
    print(f"{name}curent num is => {num}")
    lock.release()
    print(f"{name}线程结束")


def main():
    print("start")
    for i in range(10):
        t = Thread(target=task,args=())
        t.start()

    print("end")

if __name__ == "__main__":
    """产生了不一致读的情况"""
    main()

Semaphore :信号量

最多允许同时N个线程执行内容
构造方法:
• Semaphore (N)
实例方法:
• acquire([timeout]): 尝试获得锁定。使线程进入同步阻塞状态。
• release(): 释放锁。使用前线程必须已获得锁定,否则将抛出异常。

import random
import time
from threading import *

num = 0
lock = BoundedSemaphore(2)
def task():
    name = current_thread().name
    print(f"{name}start")
    time.sleep(1)
    print(f"{name}需要锁了")
    global num
    #加锁
    with lock:
        num += 1
        print(f"{name}curent num is => {num}")
        time.sleep(random.random())
        print(f"{name}curent num is => {num}")

    print(f"{name}线程结束")


def main():
    print("start")
    for i in range(10):
        t = Thread(target=task,args=())
        t.start()

    print("end")

if __name__ == "__main__":
    main()

Event: 事件锁

• 事件机制:全局定义了一个“Flag”
• 如果“Flag”的值为False,那么当程序执行wait方法时就会阻塞
• 如果“Flag”值为True,那么wait方法时便不再阻塞。
• 这种锁,类似交通红绿灯(默认是红灯),它属于在红灯的时候一次性阻挡所有线程,在绿灯的时
候,一次性放行所有的排队中的线程。
• Event是线程间通信最间的机制之一:
• 一个线程发送一个event信号,其他的线程则等待这个信号。
用于主线程控制其他线程的执行。
实例方法:
• e.wait([timeout]) : 堵塞线程,直到Event对象内部标识位被设为True或超时(如果提供了参数
timeout)
• e.set() :将标识位设为Ture
• e.clear() : 将标识伴设为False
• e.isSet() :判断标识位是否为Ture


from threading import *


event = Event()
def task():
    name = current_thread().name
    print(f"{name}start")
    #检查当前event的flag是不是true,如果是True,继续往后执行
    event.wait()
    print(f"{name}end")



def main():

    for i in range(10):
        t = Thread(target=task,args=())
        t.start()
    event.clear()
    while True:
        flag = input("是否放行(1放行,0不放行,其他退出):")
        if flag == "1":
            event.set()
            break
        elif flag == "0":
            event.clear()
        else:
            break

if __name__ == "__main__":
    main()

Condition: 条件锁

该机制会使得线程等待,只有满足某条件时,才释放n个线程。
实例方法:
• wait_for(func): 等待函数返回结果,如果结果为True-放行一个线程
• wait、lock.notify(N): 一次性放行N个wait
• acquire、release: 以上的wait和wait_for需要在锁中间使用

from threading import *


lock = Condition()
def task():
    name = current_thread().name
    print(f"{name}start")
    with lock:
        lock.wait()
        print(f"{name}end")



def main():
    for i in range(10):
        t = Thread(target=task,args=())
        t.start()
    while True:
        n = input("请输入数字,表示放行几个线程,输出其他则退出")
        if n.isdigit():
            n = int(n)
            with lock:
                lock.notify(n)
        else:
            break

if __name__ == "__main__":
    main()

from threading import *
lock = Condition()
def condition():
    flag = input("请输入0或1表示放行或者不放行")
    if flag == "0":
        return False
    else:
        return True

def task():
    name = current_thread().name
    print(f"{name}=>start")
    with lock:
        lock.wait_for(condition)
        print(f"{name}=>end")



def main():
    for i in range(10):
        t = Thread(target=task,args=())
        t.start()


if __name__ == "__main__":
    main()

GIL

Python GIL与多线程
• GIL全称Global Interpreter Lock(全局解释器锁)
• GIL和Python语言没有任何关系,只是因为历史原因导致在官方推荐的解释器Cpython中遗留的问
题(Jpython无此类问题)
• 每个线程在执行的过程中都需要先获取GIL,保证同一时刻只有一个线程可以执行代码
在这里插入图片描述
Python中多线程
• GIL最基本的行为只有下面两个:

  1. 当前执行的线程持有GIL
  2. 当线程遇到io阻塞时,会释放GIL
    • 由于GIL锁的限制,所以多线程不适合计算型任务,而更适合IO型任务
    • 计算密集型任务:用CPU、计算
    => 多进程
    • IO密集型任务:网络IO(抓取网页数据)、磁盘操作(读写文件)、键盘输入… => 多线程+多进程

进程与多进程

进程
• 概念:进程(Process)是计算机中的程序关于某数据
集合上的一次运行活动,是系统进行资源分配的基本单
位。
• 多个进程同时执行时,每个进程的执行都需要由操作系
统按一定的算法(RR调度、优先数调度算法等)分配
内存空间。
• 组成:进程控制块PCB、数据段、正文段
• 基本状态:就绪状态、运行状态和阻塞状态
在这里插入图片描述

进程
• 创建:用户创建出来的所有进程都是由操作系统负责,新进程的创建都是由一个已经存在的进程执
行了一个用于创建进程的系统调用而创建的
Linux中pid为0的进程,是所有进程的主进程
• 如何创建子进程?
在python中,每一个运行的程序都有一个主进程,可以利用模块中封装的方法来创建子进程
(os.fork =>linux、multiprocessing)

os.fork创建子进程

os.fork中就用来创建子进程的方法
注意:这个os.fork()方法只有在unix系统中才会有,在window下没有。
• 使用fork创建子进程后,操作系统会将当前的进程复制一份
• 原来的进程称为父进程,新创建的进程称为子进程
• 两个进程会各自互不干扰的执行下面的程序
• 父进程与子进程的执行顺序与系统调度有关
• 在子进程内,这个方法会返回0;在父进程内,这个方法会返回子进程的编号PID

• os.fork的返回值:
• 返回值为大于0时,此进程为父进程,且返回的数字为子进程的PID;
• 当返回值为0时,此进程为子进程。
• 如果返回值为负数则表明创建子进程失败。
• 父进程结束时,子进程并不会随父进程立刻结束。同样,父进程不会等待子进程执行完。
os.getpid():获取进程的进程号。
os.getppid():获取父进程的进程号

import os
import time
pid = os.fork()
print(f"{pid} is pid")
if pid == 0:
    print("这是一个子进程")
    print("子进程的pid:",os.getpid())
    print("父进程的pud:",os.getppid())
elif pid > 0:
    print("这是一个父进程")
    print("子进程的pid:", os.getpid())
    print("父进程的pud:", os.getppid())
else:
    print("创建失败")

time.sleep(1)

#输出结果
[root@lirixiang ~]# vim t.py 
YouCompleteMe unavailable: requires Vim 7.4.1578+.
Press ENTER or type command to continue
[root@lirixiang ~]# python3 t.py 
11831 is pid#子进程的pid
这是一个父进程
子进程的pid: 11830#父进程的pid
父进程的pud: 11799
0 is pid
这是一个子进程
子进程的pid: 11831
父进程的pud: 11830
小 案 例

在Linux上使用os.fork创建子进程
观察:ps可查看到两个进程
在这里插入图片描述

multiprocessing.Process

由于windows没有fork调用,python提供了multiprocessing支持跨平台版本。
创建管理进程模块:
Process(用于创建进程模块)
Pool(用于创建管理进程池)
Queue(用于进程通信,资源共享)
• Value,Array(用于进程通信,资源共享)
• Pipe(用于管道通信)
• Manager(用于资源共享)

Process 类
构造方法:Process([group [, target [, name [, args [, kwargs]]]]])
• group: 线程组,目前还没有实现,库引用中提示必须是None;
• target: 要执行的方法;
• name: 进程名;
• args/kwargs: 要传入方法的参数。

实例方法:
• p.start():启动进程,并调用该子进程中的p.run()
• p.run(): strat()调用run方法,如果实例进程时未制定传入target,这star执行t默认run()
• p.terminate(): 不管任务是否完成,立即停止工作进程
• p.is_alive(): 如果p仍然运行,返回True
• p.join([timeout]): 阻塞当前上下文环境的进程,直到调用此方法的进程终止或到达指定的timeout

import time
from multiprocessing import *

li = []
def task(i):
    process_name = current_process().name
    print("start",process_name)
    li.append(i)
    print(li)
    time.sleep(10)
    print("end",process_name)

if __name__ == "__main__":
    result = []
    for i in range(10):
        p = Process(target=task,args=(i,))
        p.start()
    #希望将各个子进程加起来,最后输出

    print("end")

小案例

使用multiprocessing.Process创建进程
使用multiprocessing.Process自定线程类创建进程类

进程间数据共享

不同进程间内存是不共享的,multiprocessing中提供以下方式实现进程间的数据交换
• Queue(用于进程通信,资源共享)
• Value,Array(用于进程通信,资源共享)
• Pipe(用于管道通信)
• Manager(用于资源共享)

使用multiprocessing.Array共享数据

• 创建Array时,需要指定数据类型
• 如:arr = Array(‘i’
, [11, 22, 33, 44])

'i’表示数据类型:“d”表示一个双精度的浮点数,
“i”表示一个有符号的整数
• 这些共享对象将被线程安全的处理,类型对应表
“c”: ctypes.c_char “u”: ctypes.c_wchar “b”: ctypes.c_byte “B”: ctypes.c_ubyte
“h”: ctypes.c_short “H”: ctypes.c_ushort “i”: ctypes.c_int “I”: ctypes.c_uint
“l”: ctypes.c_long,
“L”: ctypes.c_ulong “f”: ctypes.c_float “d”: ctypes.c_double

使用multiprocessing.Manager共享数 据

• 由Manager()返回的manager提供 list, dict, Namespace, Lock, RLock, Semaphore,
• BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array类型的支持。
• Manager比Array要好用一点,因为它可以同时保存多种类型的数据格式

使用multiprocessing.Queue共享数据

• 消息队列:multiprocessing.Queue
Queue是对进程安全的队列,可以使用Queue实现对进程之间的数据传输;还有一个重要作用是作
为缓存使用。
Queue(maxzize = 0) 创建一个队列对象,maxsize 表示队列中最多存放消息的数量。

• 实例方法:
• put(obj [, block=True[, timeout]]):调用队列对象的put()方法将obj插入到队列中
• get([block=True[, timeout]]):get方法可以将队列中读取并删除一个元素
• full():判断队列是否为满
• empty():判断队列是否为空
• qsize():获取队列中消息数量
Queue不能再Pool进程池中使用,使用Multiprocessing.Manager类可以适用Pool类

from multiprocessing import *


def task(q):
    if not q.empty():
        print("获取一个数据",q.get())

if __name__ == "__main__":
    q = Queue()
    for i in range(10):
        q.put(i)
        q.put(i+10)
        p = Process(target=task,args=(q,))
        p.start()
    print("end")

进程锁

使用multiprocessing.Queue共享数据
• 不一致读
• 为了防止和多线程一样的出现数据抢夺和脏数据的问题,同样需要设置进程锁。与threading类
似,在multiprocessing里也有同名的锁类RLock, Lock, Event, Condition, Semaphore,连用法
都是一样样的!
• 当创建进程时(非使用时),共享数据会被拿到子进程中,当进程中执行完毕后,再赋值给原值

multiprocessing.Pool进程池

多进程资源消耗
一般我们是通过动态创建子进程(或子线程)来实现并发服务器的,但是会存在这样一些缺点:

  1. 动态创建进程(或线程)比较耗费时间,消耗cpu资源,这将导致较慢的服务器响应。
  2. 动态创建的子进程通常只用来为一个客户服务,这样导致了系统上产生大量的细微进程(或
    线程)。进程和线程间的切换将消耗大量CPU时间。
  3. 动态创建的子进程是当前进程的完整映像,当前进程必须谨慎的管理其分配的文件描述符和
    堆内存等系统资源,否则子进程可能复制这些资源,从而使系统的可用资源急剧下降,进而
    影响服务器的性能。
    所以呢,就引入了进程池的概念。

Pool 进程池
• 进程池的作用:有效的降低频繁创建销毁线程所带来的额外开销。

进程池的原理:
• 进程池都是采用预创建的技术,在应用启动之初便预先创建一定数目的进程。
• 应用在运行的过程中,需要时可以从这些进程所组成的进程池里申请分配一个空闲的进程,来执
行一定的任务,任务完成后,并不是将进程销毁,而是将它返还给进程,由线程池自行管理。
• 如果进程池中预先分配的线程已经全部分配完毕,但此时又有新的任务请求,则进程池会动态的 创建新的进程去适应这个请求。
• 某些时段应用并不需要执行很多的任务,导致了进程池中的线程大多处于空闲的状态,为了节省
系统资源,进程池就需要动态的销毁其中的一部分空闲进程
• 进程需要一个管理者,按照一定的要求去动态的维护其中进程的数目。

Pool 主进程管理进程的机制:
• 最简单、最常用的算法是随机算法Round Robin(轮流算法)
• 主进程和所有子进程通过一个共享的工作队列来实现同步:子进程都睡眠在该工作队列上,当有
新的任务到来时,主进程将任务添加到工作队列中。这将唤醒正在等待任务的子进程,不过只有
一个子进程将获得新任务的“接管权”
,它可以从工作队列中取出任务并执行之,而其他子进程
将继续睡眠在工作队列上。
• 当选择好子进程后,主线程程还需要使用某种通知机制来告诉目标子进程有新任务需要处理,并
传递必要的数据。我们可以把这些数据定义为全局,那么它们本身就是被所有进程共享的。对于
进程池而言,最简单的方式是,在父进程和子进程之间预先建立好一条管道,然后通过管道来实
现所有的进程间通信。

Pool 进程池的应用场景
• 需要大量的进程来完成任务,且完成任务的时间比较短。
• 但对于长时间的任务,比如一个Telnet连接请求,进程池的优点就不明显了。因为Telnet会话时
间比线程的创建时间大多了。

Pool 类
构造方法
• Pool([processes[, initializer[, initargs[, maxtasksperchild[, context]]]]])
• processes :使用的工作进程的数量,如果processes是None那么使用 os.cpu_count()返回的
数量。
• initializer: 如果initializer是None,那么每一个工作进程在开始的时候会调用
initializer(*initargs)。
• maxtasksperchild:工作进程退出之前可以完成的任务数,完成后用一个新的工作进程来替代
原进程,来让闲置的资源被释放。maxtasksperchild默认是None,意味着只要Pool存在工作进
程就会一直存活。

Pool 类
实例方法
• apply_async(func[, args[, kwds[, callback]]]) 它是非阻塞。
• apply(func[, args[, kwds]])是阻塞的。
• close() 关闭pool,使其不在接受新的任务。
• terminate() 关闭pool,结束工作进程,不在处理未完成的任务。
• join() 主进程阻塞,等待子进程的退出, join方法要在close或terminate之后使用。这样是因为被
终止的进程需要被父进程调用wait(join等价与wait),否则进程会成为僵尸进程

注意:
① 使用Pool创建进程池对象,同时进程池中进程已经启动
② 向进程池对象中添加事件,事件排队执行
③ 如果主进程退出,则进程池中所有进程都退出

import time
from multiprocessing import *

def task():
    print(current_process().name,"start")
    time.sleep(0.5)

if __name__ == "__main__":
    p = Pool(4)
    for i in range(10):
       p.apply_async(func=task)

    #进程池不再接收新的任务
    p.close()

    # 等待子进程执行完毕以后,关闭进程池
    p.join()



小案例

进程池的基本使用
完成10000个计算fun的任务
对比使用Poll和Process的差异
Python中多进程与多线程
• 多进程与多线程的选择

  1. io密集型计算用多线程
  2. cpu密集型计算用多进程
多进程与多线程的生产者消费者模式

什么是生产者消费者模式
在这里插入图片描述

什么是生产者消费者模式
在这里插入图片描述
为什么要用生产者消费者模式解决问题?好处?
效率更高
增加项目的可扩展件
减少系统之间的耦合性

什么场景下使用生产者消费者模式?
有明显的两个角色(创佳效据私处埋数据)

小案例

多线程的生产者消费者模式
多进程的生产者消费者模式

from multiprocessing import *
import os,time

def producer(q):
    """生产者,把数据放入消息队列的人"""
    for i in range(20):
        time.sleep(1)
        msg = f"{os.getpid()}生产数据"
        q.put(msg)
        print(msg)


def consumer(q):
    """消费者,从消息队列取出数据的人"""
    for i in range(10):
        #当q.get没有数据,阻塞状态
        time.sleep(2)
        q.get()
        msg = f"-----------{os.getpid()}消费了数据"
        print(msg)

if __name__ == "__main__":
    q =Queue()
    #创建生产者
    p1 = Process(target=producer,args=(q,))
    p2 = Process(target=producer,args=(q,))

    p1.start()
    p2.start()

    #创建消费者
    c1 = Process(target=consumer,args=(q,))
    c2 = Process(target=consumer,args=(q,))

    c1.start()
    c2.start()
#生产者消费者模式的好处,可扩展性=>以便生产和消费平衡状态
#celery => 产生数据太大,导致redis中数据堆积
#让系统可以发挥最大的性能,同时保证资源不浪费=>增加项目的可拓展性


有顺序Queue

协程

概念

• 协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈
• 协程,又称微线程,纤程,英文名Coroutine
• 协程的作用:
• 在执行函数A时,可以随时中断,去执行函数B,然后中断继续执行函数A(可以自由切换)。
• 但这一过程并不是函数调用(没有调用语句)
在这里插入图片描述

协程与线程比较

• 线程有自己的上下文,切换受系统控制;而协程有自己的上下文,但是其切换由用户控制,由当前协
程切换到其他协程由当前协程来控制。

• 无需原子操作锁定及同步的开销,所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦
开始,就一直运行到结束,中间不会有任何切换到另一个线程的动作。
线程=>切换出os控制
协程=>切换由user控制

原子操作(不会被线稈调度札制扑断),协程的执行过程中,不会切换到另外一一个线稈动作

• 协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。

• 与多线程比协程有极高的执行效率,不需要多线程的锁机制。

• 协程以后主要用在网络爬虫和网络请求,开辟一个协程大概需要5k空间,开辟一个线程需要512k空间, 开辟一个进程占用资源最多。

• 强调非阻塞异步并发的一般都是使用协程

协程与线程的比较

假设有一个操作系统,是单核的,系统上没有其他的程序需要运行,有两个线程 A 和 B 。

• A 和 B 在单独运行时都需要 10 秒来完成自己的任务,而且任务都是运算操作,A B 之间也没有竞争和共享数据的问题。

• 现在 A B两个线程并行,操作系统会不停的在 A B 两个线程之间切换,达到一种伪并行的效果,假
设切换的频率是每秒一次,切换的成本是 0.1 秒(主要是栈切换),总共需要 20 + 19 * 0.1 = 21.9
秒。

• 如果使用协程的方式,可以先运行协程 A ,A 结束的时候让位给协程 B ,只发生一次切换,总时间
是 20 + 1 * 0.1 = 20.1 秒。

协程的缺点:
• 无法利用多核资源

python对协程的支持

• python2.x协程应用:
• yield
• gevent
• python3.x协程应用:
• asynico + yield from(python3.4)
• asynico + await(python3.5)
• gevent
• Python3.4以后引入了asyncio模块,可以很好的支持协程

协程—yield
从句法上看,协程与生成器类似,都是定义体中包含 yield 关键字的函数, 在协程中yield通常出现在表达式的右边,可以产出值,也可以不产出,如果 yield 关键字后面没有表达式,那么生成器产出 None
协程可能会从调用方接收数据,不过调用方把数据提供给协程使用的是 .send(datum) 方法,而不是next(…) 函数.

asyncio协程
• asyncio是一个使用async / await语法编写并发代码的库。
• asyncio用作多个Python异步框架的基础,这些框架提供高性能的网络和Web服务器,数据库
连接库,分布式任务队列等。
• asyncio通常非常适合IO绑定和高级结构化网络代码。

官方帮助:https://docs.python.org/3/library/asyncio.html?highlight=async#module-asyncio

asyncio提供了一组高级 API:
• 同时运行Python协同程序并完全控制它们的执行;
• 执行网络IO和IPC ;
• 控制子过程 ;
• 通过队列分配任务;
• 同步并发代码;

此外,还有一些用于库和框架开发人员的低级 API :
• 创建和管理事件循环,提供异步的 hronous API networking,运行subprocesses,处理等;OS signals
• 使用传输实现有效的协议 ;
• 使用 async / await语法桥接基于回调的库和代码。

asyncio的几个概念
• event_loop 事件循环:程序开启一个无限的循环,程序员会把一些函数(协程)注册到事件循环
上。当满足事件发生的时候,调用相应的协程函数。
• coroutine 协程:协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。
• future 对象: 代表将来执行或没有执行的任务的结果。它和task上没有本质的区别
• task 任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含任务的各种状态。Task 对象是 Future 的子类,它将 coroutine 和 Future 联系在一起,将
coroutine 封装成一个 Future 对象。
• async/await 关键字:python3.5 用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。其作用在一定程度上类似于yield。

小 案 例

协程的基本使用

import asyncio

async def nested():
    return "nested ok"

async def say_after(delay,what):
    #休息delay秒
    #调用协程函数
    await  asyncio.sleep(delay)
    print(what)
    return "WoW!"

#定义一个协程函数
async  def main():
    #调用协程函数
    print(await say_after(1,"sanchuang!"))
    #调用协程函数
    task =asyncio.create_task(say_after(2,"world!"))
    print(await task)
    print(await nested())

asyncio.run(main())

协程asyncio串行执行多任务
协程asyncio并行执行多任务

import asyncio

async def do_some_work(x):
    #sleep时长,协程执行效果,x sleep时间
    await asyncio.sleep(x)
    print(f"finished{x} second")
    return x


#创建协程对象
task1 = do_some_work(1)
task2 = do_some_work(2)
task3 = do_some_work(4)

#print(task1,type(task1))
#将协程对象定义任务列表
tasks = [
    asyncio.ensure_future(task1),
    asyncio.ensure_future(task2),
    asyncio.ensure_future(task3),
]

#调用协程函数
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

#获取结果
for task in tasks:
    print(task.result())

线程协程运行效率对比

"""
计算型任务=>多线程不如单线程,多线程不如协程 i
为什么=>本身是CPU型任务,那么多线程额外消耗CPU切换和GIL锁的时间....
I0型任务=>
1.任务内容=> sLeep(1)秒
2.创建10000个任务
3.记录消耗时间
"""
#协程
import asyncio
async def task():
    await asyncio.sleep(1)

#创建1w个协程对象
coroutines = [task() for i in range(10000)]
#转换任务
tasks = [asyncio.ensure_future(coro) for coro in coroutines]

#执行任务
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

#结果
real 0m5.883s
user 0m2. 949s
sys 0m2.395s

#线程
import time
import threading

def task():
    time.sleep(1)

for i in range(20000):
    t = threading.Thread(target=task)
    t.start()

#结果
rea1 0m1.844s
user 0m0.9743
sys OmO.048s

协程vs线程
1.协程=>用户控制任务调度,原子性(不会被操作系统切换出去)
线程=> os操控任务调度
2.资源消耗=>迸程>线程>协程
3. 协程的优势?多进程+协程
python对协程的支持3.5

阻塞=>任务来了->需要其他资源->如果资源没有到位-> - 直等待
同步阻塞=>一 次执行- 一个任务,依次执行
异步非阻塞=> 多个任务可以同时进行,如果任务 需要资源了,切换到其他任务 去执行

协程队列(https://docs.python.org/3/library/asyncio-queue.html#asyncio-queues)
在这里插入图片描述

协程锁(https://docs.python.org/3/library/asyncio-sync.html#asyncio-sync)
在这里插入图片描述

课 后 作 业

批量扫描指定网段内的存活IP
• 网段:192.168.0.0/24 (192.168.0.1~192.168.0.255)
• 考虑如何才能更快速地扫描出结果
• 前置模块:IPy, ping命令的使用

什么是GIL
什么是进程、线程,两者关系如何
现在有t1、t2、t3三个线程,你怎样保证t2在t1执行完后执行,t3在t2执行完后执行

函数A在不停产生数据(数据来源于文件),函数B用处理A产生的数据,B处理每次处理数据都需要2s。
●请使用分别使用生产者消费者模式,1个生产者,4个消费者
●函数A,实现产生数据(从文件中取出数据)
●函数B,实现处理数据(将数据转化为大写后, sleep 2s,写入文件)
●需要保证生成的目标文件,数据顺序与原来保持一致

发布了71 篇原创文章 · 获赞 9 · 访问量 3326

猜你喜欢

转载自blog.csdn.net/qq_45206551/article/details/105345596