学习时间:9:00——11:00 16:00——19:30
补充概念:进程、线程
进程:一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。
线程:进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。
本质区别:Windows系统中,进程只是资源的分配单位,而线程才是CPU调度运行的基本单位。线程也被称为轻量级进程。
举个例子,进程就像是一个工厂,而线程就是工厂里的流水线,一个工厂里可以有多条流水线,这些流水线共享资源和数据,而不同进程也就是不同的工厂,有各自独立的内存空间,相互独立。
多个进程的程序,调度依然是按照多个线程去进行调度,由于CPU时间片分配给每个独立调度的线程,拥有四个线程的进程比拥有一个线程的进程会拥有更多的CPU时间片,就像原本只有一条流水线,现在同时有四条流水线运行,自然也就达到了加速程序的效果。就CPU利用率来讲,如果一个有四个线程的进程运行在一个四核的CPU机器上,那么核的利用率可以达到100%,即所有的核都可以调度运行一个线程, 不会出现一方有难,八方围观的情况。同样,四个单线程进程也能使四核的CPU机器计算资源利用率达到100%,因为每个进程中的线程被独立调度执行。
一个进程崩溃,并不会影响其他的进程,这也很好理解,因为进程是一个个独立运行的程序,彼此独立,就像是不同行业的工厂,一个倒闭,其余的并不受影响,而线程不然,一个流水线的加工出现问题,那么整个工厂生产的产品都会受到影响,进而整个工厂倒闭。
1.协程
协程不是计算机提供,程序员人为创造。
协程(Coroutine),也可以被称为微线程,是一种用户态内的上下文切换技术。简而言之,其实就是通过一个线程实现代码块相互切换执行。比线程更加轻量级,一个线程也可以拥有多个协程。
实现协程有这么几种方法:
greenlet,早期模块
yield关键字
asyncio装饰器(python3.4及以后,但3.8后被弃用)
async&await关键字(python3.5及以后)(推荐方法)
现在常用greenlet和async&await关键字
greenlet实现协程
from greenlet import greenlet
def func1():
print(1)
gr2.switch() #切换到func2函数
print(2)
gr2.switch()
def func2():
print(3)
gr1.switch() #切换到func1函数
print(4)
gr1 = greenlet(func1)
gr2 = greenlet(func2)
gr1.switch() #先执行func1
#输出结果:1、3、2、4
yield关键字实现协程
def func1():
yield 1
yield from func2() #切换到func2
yield 2
def func2():
yield 3
yield 4
f1 = func1()
for item in f1:
print(item)
#输出结果:1、3、4、2
async&await实现协程
import asyncio
async def func1():
print(1)
await asyncio.sleep(1) #遇到IO耗时操作,自动切换到tasks的其他任务中
print(2)
async def func2():
print(3)
await asyncio.sleep(1) #遇到IO耗时操作,自动切换到tasks的其他任务中
print(4)
tasks = [asyncio.ensure_future(func1()),asyncio.ensure_future(func2())]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
#输出结果:1、3、2、4
原理是导入asyncio模块,之后将定义好的多个函数传到asyncio.ensure_future()的参数中,然后将他们封装在一个任务列表中,然后调用这个任务列表。
这个协程的巧妙之处在于遇到阻塞操作时可以自动切换任务,假如分别在func1的print(1)语句、func2的print(3)语句后面均加上一个下载图片(假设图片下载时间为1秒)的网络IO请求,那么这段代码执行时,会先打印1,然后发送第一个下载请求,由于阻塞,会自动切换到func2中,打印3,然后再次发送第二个打印请求,此时经过1秒钟,图片1、2均下载完成,相比单线程操作节省了1秒钟!!
这样做在于充分压榨单线程的时间,达到多线程的效果,弊端是会频繁访问,加大了ip被封掉的可能性,所以需要ip伪装
协程的意义:在一个线程中如果遇到IO等待时间,线程不会傻等,而是去干点别的事。