使用场景:
爬虫(1.从目标站点下载网页数据 2.用re从字符串中提取你需要的数据)
什么式异步回调?
a 交给b一个任务, b在执行完成后回过头来调用了a的一个函数,称之为回调函数
通常异步任务都会和回调函数一起使用
使用方式: 通过add_done_callback() 给 future
为什么要用异步回调
需要获取异步任务的结果,但是又不应该阻塞(降低效率)
高效的获取任务结果
''' 1.从目标站点下载网页数据 本质就是HTML格式字符串 2.用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)
2.线程队列
线程队列的本身就是一个普通容器,不能被进程共享
线程队列与进程队列的却别:
进程队列可以被多进程共享,而线程队列则本身就是一个普通容器,不能被进程共
享
先进先出队列:Queue q = Queue(1) q.put("a") q.put("b",timeout=1) print(q.get()) print(q.get(timeout=2)) 后进先出队列: LifoQueue lq = LifoQueue() lq.put("a") lq.put("b") lq.put("c") print(lq.get()) print(lq.get()) print(lq.get()) 优先级队列: PriorityQueue() 取出顺序式是,由小到大,优先级可以是数字或字符,只要能够比较大小即可 pq = PriorityQueue() pq.put((["a"],"bdslkfjdsfjd")) pq.put((["b"],"csdlkjfksdjkfds")) pq.put((["c"],"asd;kjfksdjfkdsf")) print(pq.get()) print(pq.get()) print(pq.get())
3.线程事件
什么是事件? 是一种通知服务,用于协调多个线程工作,当一个线程需要执行某个操作,需要获取另外一个线程的状态时的通知服务就是事件
''' 以TCP服务器与客户端连接为例 ''' import time from threading import Thread from threading import Event # 创建一个事件 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: #如果3次都没成功 就打印这个消息 print("服务器没有启动") Thread(target=start).start() Thread(target=connect).start()
4.单线程下实现并发效果
首先要明确定义:
并发:指的是多个任务同时发生,看起来好像是同时都在进行
并行: 指的是多个任务真正的同时进行
单线程下通过生成器完成并发>>>生成器的特点是:只要函数中出现yield该函数就变成了生成器
单线程并发是为了提高效率,对于计算密集类型任务,单线程并发会降低效率
对于IO密集型的任务,单线程并发会提高效率(在执行IO操作的时候,切换到其他计算任务,提高CPU的占用率)
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(10) next(g) task2() ''' 对于计算型任务 单线程并发效率较低 ''' 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) 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)
5.greenlet
greenlet主要封装了生成器,使得在使用生成器是实现并发时,简化代码
该模块简化了yield复杂的代码结构,实现了单线程下多任务并发,但是无论直接使用yield还是greenlet都不能检测IO操作,遇到IO时同样进入阻塞状态,所以此时的并发是没有任何意义的。
import greenlet import time def task1(): print('task1 run') time.sleep(10) g2.switch() print('task1 run') def task2(): print('task2 run') g1.switch() g1 = greenlet.greenlet(task1) g2 = greenlet.greenlet(task2) g1.switch()
6.协程
什么是协程?
协程是单线程下的并发,又称微线程,纤程
协程可以理解为提高线程工作效率,本质是单线程并发
协程也称之为微线程(它比线程更轻量级 单线程下任务的切换 比操作系统切换线程要简单的多)
为什么要有协程?
因为CPython中无法实现并行执行任务,导致效率降低,需要一种方法将效率最大化
协程的优点:
1.协程的切换开销更小,属于程序级别的切换,操作系统完全能感知不到,因而更加轻量级
2.单线程内就可以实现并发的效果,最大限度利用CPU
协程的缺点:
1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
2.协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
怎么使用?
Python中 使用Gevent模块来 实现协程 其能在多个任务间进行切换 而且能够自己检测IO
from gevent import monkey monkey.patch_all() import gevent import time def task1(): print("task1 run") time.sleep(10) print("task1 run") def task2(): print("task2 run") print("task2 run") g1 = gevent.spawn(task1) g2 = gevent.spawn(task2) # g1.join() # g2.join() gevent.joinall([g1,g2])