线程&线程池
线程
进程和线程:
进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。
注意:两个都是过程
线程一个特点:
一个进程中,多个线程共享资源
线程和进程区别:
1. 线程的创建开销小于进程, 创建速度快
2. 同一进程下的多个线程共享该进程的地址空间(资源)
主线程影响其他线程:
是因为线程的概念:
一个进程内默认就会有一个控制线程,该控制线程可以执行代码从而创建新的线程,
该控制线程的执行周期就代表该进程的执行周期
1.一个进程内不开子进程,也不开'子线程' : 主线程结束,该进程就结束
2.当一个进程内开启子进程时 : 主线程结束,主进程要等,等所有子进程运行完毕,给儿子收尸.
3.当一个进程内开启多个线程时 :
主线程结束并不意味着进程结束,
进程的结束指的是该进程内所有的线程都运行完毕,才应该回收进程.
简单点说,就是:
主进程等子进程, 是主进程活都干完了
主线程等子线程,是主进程还没有干完活
(所有的线程都干完活了,该进程才算干完活了)
子进程和线程的id号 验证内存空间
import os from multiprocessing import Process n = 100 def func(): global n n = 1 print('子',n,os.getpid()) if __name__ == '__main__': p = Process(target=func,) p.start() print('主',n,os.getpid()) # 主 100 8852 主进程和子进程的id号不一样,说明进程间的内存空间是隔离的 # 子 1 4716 子进程修改变量,主进程感觉不到,说明进程间的内存空间是隔离的
全局变量的修改,验证内存空间
import os from threading import Thread n = 100 def func(): global n n = 1 print(current_thread().getName(), n, os.getpid()) if __name__ == '__main__': t = Thread(target=func,name = ‘func’) #给子线程起名字,不写,默认 Thread-1.Thread-2..... t.start() print(current_thread().getName(), n, os.getpid()) # func 1 1088 子线程和主线程的id号一样,说明主线程和子线程在一个进程空间里 # MainThread 1 1088 子线程修改变量n = 1,主线程能感知到,说明线程间共享数据
进程可以看进程号
线程只能看名字了
getName() 测试的时候用的较多
enumerate() 以列表的形式列出当前所有活着的线程
active_count() 统计当前所有活着的线程的个数
import time from threading import Thread, active_count, enumerate 需要导入threading模块中这两个方法 def func(): time.sleep(3) if __name__ == '__main__': t = Thread(target=func) t.start() print(enumerate()) print(active_count()) # [<_MainThread(MainThread, started 9212)>, <Thread(Thread-1, started 1612)>] 列表形式 # 2 ------------------------------------- print(enumerate()[0].getName()) #enumerate()是一个列表,取索引值[0],再通过getName(),一样能拿到线程的名字 print(current_thread().getName()) #MainThread #MainThread
os.cpu_count() 查看cpu核数
import os print(os.cpu_count())
线程池
t = ThreadPoolExecutor() 创建线程池的时候,如果不设置数量,默认是cpu核数*5
证明线程池不设置参数,默认值 是cpu核数*5
import time import os from threading import ac-tive_count from concurrent.futures import ThreadPoolExecutor def func(): time.sleep(1) if __name__ == '__main__': t = ThreadPoolExecutor() #创建线程池的时候,如果不设置数量,默认是cpu核数*5 for i in range(100): t.submit(func,) print(os.cpu_count()) #cpu核数 print(active_count()) #子线程数 + 主线程 #4 #21
进程池不要超过cpu核数的2倍(不是真理,具体会根据程序问题的增多,数目相应调大,也和电脑的性能有关)
这个参数肯定不能再程序里面写死,要写到配置文件里,要让软件的使用者( 给运维人员,不是直接给客户)根据他自己的业务场景以及他硬件的性能,要做相应的调整,取配置,所有的支
持并发的软件都是这样做,都应该这样做.不应该在程序里面写死.
多线程+异步调用+requests
requests 库
HTTP客户端库(编写爬虫和测试服务器响应数据时经常用到)
安装:pip install requests
get 请求:
r = requets.get('http://www.baidu.com')
r.text 返回headers中的编码解析的结果
异步调用:
(回调机制:任务执行完,触发回调函数,同时把任务结果传给回调函数)
提交完任务,(为该任务绑定一个回调函数),不用在原地等任务执行完毕拿到结果,可以直接提交下一个任务.
一个任务一旦执行完毕就会自动触发回调函数的运行
不在原地等结果,那么结果哪里去了?
就是通过回调机制
回调函数:add_done_callback(func)
回调函数的参数是单一的:
回调函数的参数就是它所绑定任务的返回值(绑定任务的return)
异步调用,不用等结果 结果去哪了 就是 回调
import requests import time from threading import current_thread from concurrent.futures import ThreadPoolExecutor url = ['https://www.baidu.com', 'https://www.jd.com', 'https://www.python.org', 'http://www.qq.com'] def get(url): print('get',current_thread().getName(),url) #current_thread().getName()打印哪些线程执行下载任务 time.sleep(2) r = requests.get(url) if r.status_code == 200: # 200代表成功下载 return {'url': url, 'text': r.text} def parse(obj): res = obj.result() print('parse',current_thread().getName(),res['url'],len(res['text'])) #打印哪些线程执行解析任务 if __name__ == '__main__': t = ThreadPoolExecutor(2) for i in url: t.submit(get,i).add_done_callback(parse) #回调函数 t.shutdown(wait=True) print(current_thread().getName()) # get ThreadPoolExecutor-0_0 https://www.baidu.com # get ThreadPoolExecutor-0_1 https://www.jd.com # parse ThreadPoolExecutor-0_0 https://www.baidu.com 2443 # get ThreadPoolExecutor-0_0 https://www.python.org # parse ThreadPoolExecutor-0_1 https://www.jd.com 124541 # get ThreadPoolExecutor-0_1 http://www.qq.com # parse ThreadPoolExecutor-0_1 http://www.qq.com 235464 # parse ThreadPoolExecutor-0_0 https://www.python.org 48856 # MainThread
一直是由线程控制处理
通过进程演示异步概念(不用开新的进程,主进程处理结果)
import requests import time import os from threading import Thread, current_thread from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor url = ['https://www.baidu.com', 'https://www.jd.com', 'https://www.python.org', 'http://www.qq.com'] def get(url): print('get',os.getpid(),url) #os.getpid()打印哪些进程执行下载任务 time.sleep(3) r = requests.get(url) if r.status_code == 200: return {'url': url, 'text': r.text} def parse(obj): res = obj.result() print('parse',os.getpid(),res['url'],len(res['text'])) #os.getpid()打印哪些进程执行解析任务 if __name__ == '__main__': t = ProcessPoolExecutor(2) for i in url: t.submit(get,i).add_done_callback(parse) t.shutdown(wait=True) print('\n',os.getpid()) # get 6972 https://www.baidu.com # get 7180 https://www.jd.com # get 6972 https://www.python.org # parse 7856 https://www.baidu.com 2443 # get 7180 http://www.qq.com # parse 7856 https://www.jd.com 124541 # parse 7856 http://www.qq.com 235464 # parse 7856 https://www.python.org 48856 # # 7856