一、异步回调
异步任务使用场景:
爬虫
- 从目标站点下载网页数据,本质就是HTML格式字符串
- 用re从字符串中提取出你需要的数据
import requests, re, os
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
from threading import current_thread
def get_data(url):
print("%s 正在请求%s" % (current_thread().name, url))
response = requests.get(url)
print("%s 请求%s成功" % (current_thread().name, url))
return response
def parser(obj):
res = obj.result()
htm = res.content.decode("utf-8")
ls = re.findall("href=.*?com", htm)
print("%s解析完成! 共%s个连接" % (current_thread().name,len(ls)))
if __name__ == '__main__':
urls = ["https://www.baidu.com",
"https://www.tmall.com",
"https://www.taobao.com",
"https://www.jd.com",
"https://www.python.org",
"https://www.apple.com"]
pool = ThreadPoolExecutor(3)
for i in urls:
obj = pool.submit(get_data, i)
obj.add_done_callback(parser)
#实现了并发请求数据,使用空闲线程解析数据
二、线程队列
- 线程队列与进程队列的区别:进程队列可以被多进程共享,而线程中的队列 就是一个普通的容器不能进程共享
from queue import Queue,LifoQueue,PriorityQueue
1.先进先出队列
q = Queue(1)
q.put('a')
print(q.get())
print(q.get(timeout=2))
2.后进先出队列
lq = LifoQueue()
lq.put('a')
lq.put('b')
lq.put('c')
print(lq.get()) #c
print(lq.get()) #b
print(lq.get()) #a
3.优先级队列 取出顺序是有小到大
pq = PriorityQueue()
pq.put((2,"b"))
pq.put((3,"c"))
pq.put((1,"a"))
print(pq.get()) #(1, 'a')
print(pq.get()) #(2, 'b')
print(pq.get()) #(3, 'c')
pq.put((["a"],"b"))
pq.put((["b"],"c"))
pq.put((["c"],"a"))
print(pq.get()) #(['a'], 'b')
print(pq.get()) #(['b'], 'c')
print(pq.get()) #(['c'], 'a')
三、线程事件
- 事件是用于协调多个线程工作的,当一个线程要执行某个操作,需要获取另一个线程的状态,你要给别人打电话必须明确知道对方手机买好了,作为客户端要连接服务器必须明确服务器已经启动了,那么作为启动服务器的一方,如何告知客户端?那么就通过事件
使用变量类完成多线程协作:
from threading import Thread
import time
is_boot = False
def start():
global is_boot
print('正在启动服务器')
time.sleep(5)
print('服务器启动成功')
is_boot = True
def connect():
while True:
if is_boot:
print('连接服务器成功')
break
else:
print('连接服务器失败')
time.sleep(0.5)
Thread(target=start).start()
Thread(target=connect).start()
from threading import Thread
from threading import Event
import time
#创建一个事件
e = Event() #默认False
def start():
print('正在启动服务器。。。')
time.sleep(5)
print('服务器启动成功')
e.set() #就是把事件的值设置为True
def connect():
#重试3次
for i in range(3):
print('等待服务器启动。。。')
e.wait(1) #会阻塞,直到对方把事件设置为True
if e.isSet():
print('连接成功')
break
else:
print('连接失败')
else:
print('服务器没有启动')
Thread(target=start).start()
Thread(target=connect).start()
四、在单线程下实现并发效果
-
通过生成器就能完成并发执行,生成器的特点只要函数中出现了yield该函数就变成了生成器
使用生成器实现单线程并发
import time
def task1():
a = 1
while True:
print('task1 run')
a += 1
print(a)
yield
def task2():
g = task1()
while True:
print('task2 run')
time.sleep(5)
next(g)
task2()
单线程并发是为了提高效率,对于计算密集型任务,单线程并发反而降低效率,对于IO密集型,如果可以在执行IO操作的时候切换到其他计算任务,就能提高CPU占用率,从而提高效率
import time
def task1():
a = 0
for i in range(10000000):
a += i
yield
def task2():
b = 0
g = task1()
for i in range(10000000):
b += i
next(g)
s = time.time()
task2()
print(time.time()-s) #耗时为2.8902969360351562
import time
def task1():
a = 0
for i in range(10000000):
a += i
def task2():
b = 0
for i in range(10000000):
b += i
s = time.time()
task1()
task2()
print(time.time()-s) #耗时为1.2698264122009277
五、greenlet
- greenlet 主要封装了生成器,使得我们在使用生成器实现并发时简化了代码
import greenlet
import time
def task1():
print("task1 run")
time.sleep(5)
g2.switch()
print("task1 run")
def task2():
print("task2 run")
g1.switch()
g1 = greenlet.greenlet(task1)
g2 = greenlet.greenlet(task2)
g1.switch()
#task1 run
#task2 run
#task1 run
六、协程
-
什么是协程:可以这么理解是协助线程更高效的工作,本质就是单线程实现并发,也称之为微线程(它比线程更轻量级,单线程下任务的切换比操作系统切换线程要简单的多)
-
为什么有协程:是因为在CPython中,无法并行执行任务,导致效率低,所以我们就需要一种方案能够将单线程的效率最大化就是协程。Python中使用Gevent模块来实现协程,其能在多个任务间进行切换,而且能够自己检测IO
from gevent import monkey
monkey.patch_all()
import gevent
import time
def task1():
print('task1 run')
time.sleep(5)
print('task1 run')
def task2():
print('task2 run')
print('task2 run')
g1 = gevent.spawn(task1)
g2 = gevent.spawn(task2)
#g1.join() #等待任务执行结束
gevent.joinall([g1,g2]) #等待所有任务结束
#注意:如果开启了一个会产生的io的任务,并且你没有执行join,那么一旦发生io,这个任务就立马结束了