Python学习之多线程与协程

Python学习之多线程与协程

概念

  • 进程

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。

  • 线程

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。

  • 协程

协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

举一个简单的例子:我们把一个餐厅当做一个进程,如果这个餐厅只有1个服务员,那么这个餐厅是 单线程的;如果这个餐厅有2个服务员,那么这个餐厅是双线程的。 如果这个餐厅的服务员像海底捞的服务员一样,一次只服务一桌的客人,在这桌的客人离开前不会服务 下一桌的客人,那么我们称这个线程是阻塞的。如果一个服务员同时服务多桌客人,那么我们称这个线程 是非阻塞的。

实现了非阻塞的线程我们称之为协程,也就是说协程是依赖线程的存在。

对比

我们先写一个本地flask应用,返回网页前等待3S,模拟真实网络情况。

1
2
3
4
5
6
7
8
9
from flask import Flask
import time
app = Flask(__name__)
@app.route('/')
def Web():
time.sleep(3)
return 'Test Web!'
if __name__ == '__main__':
app.run(debug=True,port=25565)

单线程

1
2
3
4
5
6
7
8
9
10
11
12
13
import  time
import requests
x=100
def html():
url='http://127.0.0.1:25565'
r=requests.get(url,timeout=10)

if __name__ =='__main__':
t0=time.time()
for i in range(0 , x):
html()
t1=time.time()
print("抓取{0}个网页,总计时间{1:.2f}s".format(x,t1-t0),end="")
耗时

img

多线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import  time
import requests
from multiprocessing.dummy import Pool as ThreadPool
x=100
thread=4
def html():
url='http://127.0.0.1:25565'
r=requests.get(url,timeout=10)
def pol(i):
for j in range(0,x//thread):
html()
if __name__ == '__main__':
t0=time.time()
i=[j for j in range(0,thread)]
pool=ThreadPool(thread)
pool.map(pol,i)
pool.close()
pool.join()
t1=time.time()
print("抓取{0}个网页,总计时间{1:.2f}s".format(x,t1-t0),end="")

耗时

img

单线程协程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import  time
import requests
import asyncio
x=100
async def html():
url='http://127.0.0.1:25565'
future=loop.run_in_executor(None, requests.get, url)
response= await future
if __name__ =='__main__':
t0=time.time()
tasks=[ asyncio.ensure_future(html()) for i in range(0,x)]
loop=asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
t1=time.time()
print("抓取{0}个网页,总计时间{1:.2f}s".format(x, t1 - t0), end="")
耗时

img

多线程协程**

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import  time
import requests
import asyncio
from multiprocessing.dummy import Pool as ThreadPool
x=100
thread=4
async def html(loop):
url='http://127.0.0.1:25565'
future=loop.run_in_executor(None, requests.get,url)
response= await future

def pol(i):
loop=asyncio.new_event_loop()
asyncio.set_event_loop(loop)
tasks= [asyncio.ensure_future(html(loop)) for i in range(0,x//thread)]
loop.run_until_complete((asyncio.wait(tasks)))
loop.close()

if __name__ == '__main__':
t0=time.time()
pool=ThreadPool(thread)
i=[j for j in range(0,thread)]
pool.map(pol,i)
pool.close()
pool.join()
t1=time.time()
print("抓取{0}个网页,总计时间{1:.2f}s".format(x, t1 - t0), end="")
耗时

img

总结

多线程和协程结合,能够使得爬虫的速度达到极致

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×