2.GIL带来的问题
由于互斥锁的特性,程序串行,保证数据安全,降低执行效率,GIL将使得程序整体效率降低
3.为什么需要GIL
多个线程将不可能在同一时间使用解释器,从而保证了解释器的数据安全。
4.关于GIL的性能讨论
优点:保证了Cpython中的内存管理是线程安全
缺点:互斥锁的特性使得多线程无法并行
单核下无论是IO密集型还是计算机密集型的GIL都不会产生任何影响
多核下对IO密集任务,GIL会有细微的影响,基本可以忽略
Cpython中IO密集任务应该采用多线程,计算密集型应该采用多进程
另外:之所以广泛采用CPython解释器,就是因为大量的应用程序都是IO密集型的,还有另一个很重要的原因是CPython可以无缝对接各种C语言实现的库,这对于一些数学计算相关的应用程序而言非常的happy,直接就能使用各种现成的算法
5.自定义的线程互斥锁与GIL的区别
GIL保护的是解释器级别的数据安全,比如对象的引用计数,垃圾分代数据等等,具体参考垃圾回收机制详解
自定义的互斥锁是对程序中自己定义的数据进行加锁,,例如程序中共享自定义的数据的时候就要自己加锁
6.线程池与进程池
1)什么是进程/线程池
池表示一个容器,本质上就是一个存储进程或线程的列表
2)线程池与进程池的使用环境
如果是IO密集型任务使用线程池,如果是计算密集任务则使用进程池
3)为什么需要进程/线程池
在很多情况下需要控制进程或线程的数量在一个合理的范围,例如TCP程序中,一个客户端对应一个线程,虽然线程的开销小,但 肯定不能无限的开,否则系统资源迟早被耗尽,解决的办法就是控制线程的数量。
7.同步异步,阻塞非阻塞
阻塞:当程序执行过程中遇到了IO操作,在执行IO操作时,程序无法继续执行其他代码,称为阻塞!
非阻塞:程序在正常运行没有遇到IO操作,或者通过某种方式使程序即时遇到了也不会停在原地,还可以执行其他操作,以提高CPU的占用率
同步-异步 指的是提交任务的方式
同步指调用:发起任务后必须在原地等待任务执行完成,才能继续执行
异步指调用:发起任务后必须不用等待任务执行,可以立即开启执行其他操作
程序中的异步调用并获取结果方式1:
from concurrent.futures import ThreadPoolExecutorfrom threading import current_threadimport timepool = ThreadPoolExecutor(3)def task(i): time.sleep(0.01) print(current_thread().name,"working..") return i ** iif __name__ == '__main__': objs = [] for i in range(3): res_obj = pool.submit(task,i) # 异步方式提交任务# 会返回一个对象用于表示任务结果 objs.append(res_obj)# 该函数默认是阻塞的 会等待池子中所有任务执行结束后执行pool.shutdown(wait=True)# 从结果对象中取出执行结果for res_obj in objs: print(res_obj.result())print("over")
程序中的异步调用并获取结果方式2:
from concurrent.futures import ThreadPoolExecutorfrom threading import current_threadimport timepool = ThreadPoolExecutor(3)def task(i): time.sleep(0.01) print(current_thread().name,"working..") return i ** iif __name__ == '__main__': objs = [] for i in range(3): res_obj = pool.submit(task,i) # 会返回一个对象用于表示任务结果 print(res_obj.result()) #result是同步的一旦调用就必须等待 任务执行完成拿到结果print("over")
什么是异步回调
为什么需要异步回调
异步回调的使
在编写爬虫程序时,通常都是两个步骤:
1.从服务器下载一个网页文件
2.读取并且解析文件内容,提取有用的数据
按照以上流程可以编写一个简单的爬虫程序
要请求网页数据则需要使用到第三方的请求库requests可以通过pip或是pycharm来安装,在pycharm中点击settings->解释器->点击+号-> 搜索requests->安装
import requests,re,os,random,timefrom concurrent.futures import ProcessPoolExecutordef get_data(url): print("%s 正在请求%s" % (os.getpid(),url)) time.sleep(random.randint(1,2)) response = requests.get(url) print(os.getpid(),"请求成功 数据长度",len(response.content)) #parser(response) # 3.直接调用解析方法 哪个进程请求完成就那个进程解析数据 强行使两个操作耦合到一起了 return responsedef parser(obj): data = obj.result() htm = data.content.decode("utf-8") ls = re.findall("href=.*?com",htm) print(os.getpid(),"解析成功",len(ls),"个链接")if __name__ == '__main__': pool = ProcessPoolExecutor(3) urls = ["https://www.baidu.com", "https://www.sina.com", "https://www.python.org", "https://www.tmall.com", "https://www.mysql.com", "https://www.apple.com.cn"] # objs = [] for url in urls: # res = pool.submit(get_data,url).result() # 1.同步的方式获取结果 将导致所有请求任务不能并发 # parser(res) obj = pool.submit(get_data,url) # obj.add_done_callback(parser) # 4.使用异步回调,保证了数据可以被及时处理,并且请求和解析解开了耦合 # objs.append(obj) # pool.shutdown() # 2.等待所有任务执行结束在统一的解析 # for obj in objs: # res = obj.result() # parser(res) # 1.请求任务可以并发 但是结果不能被及时解析 必须等所有请求完成才能解析 # 2.解析任务变成了串行
总结:异步回调使用方法就是在提交任务后得到一个Futures对象,调用对象的add_done_callback来指定一个回调函数,
如果把任务比喻为烧水,没有回调时就只能守着水壶等待水开,有了回调相当于换了一个会响的水壶,烧水期间可用作其他的事情,等待水开了水壶会自动发出声音,这时候再回来处理。水壶自动发出声音就是回调。
注意:
-
使用进程池时,回调函数都是主进程中执行执行
-
使用线程池时,回调函数的执行线程是不确定的,哪个线程空闲就交给哪个线程
-
回调函数默认接收一个参数就是这个任务对象自己,再通过对象的result函数来获取任务的处理结果
1.Queue 先进先出队列
与多进程中的Queue使用方式完全相同,区别仅仅是不能被多进程共享。
2.LifoQueue 后进先出队列
该队列可以模拟堆栈,实现先进后出,后进先出
3.PriorityQueue 优先级队列
该队列可以为每个元素指定一个优先级,这个优先级可以是数字,字符串或其他类型,但是必须是可以比较大小的类型,取出数据时会按 照从小到大的顺序取出
事件表示在某个时间发生了某个事情的通知信号,用于线程间协同工作。
因为不同线程之间是独立运行的状态不可预测,所以一个线程与另一个线程间的数据是不同步的,当一个线程需要利用另一个线程的状态来确定自己的下一步操作时,就必须保持线程间数据的同步,Event就可以实现线程间同步
Event介绍
event.isSet():返回event的状态值;event.wait():将阻塞线程;知道event的状态为Trueevent.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;event.clear():恢复event的状态值为False。
使用案例:
# 在链接mysql服务器前必须保证mysql已经启动,而启动需要花费一些时间,所以客户端不能立即发起链接 需要等待msyql启动完成后立即发起链接from threading import Event,Threadimport timeboot = Falsedef start(): global boot print("正正在启动服务器.....") time.sleep(5) print("服务器启动完成!") boot = True def connect(): while True: if boot: print("链接成功") break else: print("链接失败") time.sleep(1)Thread(target=start).start()Thread(target=connect).start()Thread(target=connect).start()
使用Event改造后
from threading import Event,Threadimport timee = Event()def start(): global boot print("正正在启动服务器.....") time.sleep(3) print("服务器启动完成!") e.set()def connect(): e.wait() print("链接成功") Thread(target=start).start()Thread(target=connect).start()Thread(target=connect).start()
增加需求,每次尝试链接等待1秒,尝试次数为3次
from threading import Event,Threadimport timee = Event()def start(): global boot print("正正在启动服务器.....") time.sleep(5) print("服务器启动完成!") e.set()def connect(): for i in range(1,4): print("第%s次尝试链接" % i) e.wait(1) if e.isSet(): print("链接成功") break else: print("第%s次链接失败" % i) else: print("服务器未启动!")Thread(target=start).start()Thread(target=connect).start()# Thread(target=connect).start()