前言
实习期间需要用到python多线程,先后使用了threading和ThreadPoolExecutor两个模块,因此简单记录一下。
一、使用需求
公司项目需要增加一个多个设备同时升级的接口,接口接收的参数是多台设备的相关信息,为了提高升级效率,需要开启多线程去做升级操作。
二、ThreadPoolExecutor实现
参考:
[python] ThreadPoolExecutor线程池
as_completed函数用例
1. 主要代码
with ThreadPoolExecutor(max_workers=20) as executor:
for device in devices['devices']:
device['reboot_dev']=reboot_dev
device['testCycleId']=testCycleId
device['firmware_type']=firmware_type
device['task_id']=str(task_id)
task = executor.submit(device_upgrade, **device)
task_list.append(task)
# #as_completed按完成顺序返回,不按添加顺序遍历
# for future in as_completed(task_list):
# # print("future:",future.__dict__)
# print("result:",future.result())
# if future.result()['success']==False:
# msg_dicts.append(dict(success='False',ip=devices['devices'][index]['ip'],msg=future.result()['msg']))
# flag=False
# else:
# msg_dicts.append(dict(success='True',ip=devices['devices'][index]['ip'],msg=future.result()['msg']))
# index+=1
#按添加顺序遍历
for future in task_list:
# print("future:",future.__dict__)
print("result:",future.result())
if future.result()['success']==False:
msg_dicts.append(dict(success=False,ip=devices['devices'][index]['ip'],msg=future.result()['msg']))
flag=False
else:
msg_dicts.append(dict(success=True,ip=devices['devices'][index]['ip'],msg=future.result()['msg']))
index+=1
2.小结
concurrent.futures模块使用起来十分简洁方便,ThreadPoolExecutor 是线程池执行者,相较于多开线程能够有更好的效率,不使用as_completed是按添加顺序遍历执行结果,使用as_completed这个生成器,实现先完成先处理,可以进一步提高效率。
三、threading实现
参考:Python中的多线程
1.引入原因
原本按照ThreadPoolExecutor的方法能够实现多线程,但是由于每次多线程升级时,cpu会跑满,导致FastApi项目自动重启,重启后会接着升级,但此期间接口会返回502,所以利用redis将升级日志存储起来,不等升级完成直接返回结果,新建一个接口去读取redis存储的实时升级日志。
但是在尝试用下面代码实现时,发现还是会阻塞主线程(也有可能是我使用方法不对):
with ThreadPoolExecutor(max_workers=20) as executor:
for device in devices['devices']:
device['reboot_dev']=reboot_dev
device['testCycleId']=testCycleId
device['firmware_type']=firmware_type
device['task_id']=str(task_id)
task = executor.submit(device_upgrade, **device)
task_list.append(task)
return {"success":True,"msg":"正在升级中..."}
因此,尝试换种方法来实现,也就是使用threading模块。
2.主要代码
for device in devices['devices']:
device['reboot_dev']=reboot_dev
device['testCycleId']=testCycleId
device['firmware_type']=firmware_type
device['task_id']=str(task_id)
task = threading.Thread(target=device_upgrade, args=(None,device['ip'],device['port'],device['username'],device['password'],device['reboot_dev'],"","","","","","","","",device['testCycleId'],device['firmware_type'],device['task_id']))
task.start()
redis_conn.hset(str(task_id), device['ip'], "begin")
print(device['ip'],"beginning!!!")
first_log=redis_conn.hget(str(task_id),'log')
print(first_log)
if str(first_log)=="None":
redis_conn.hset(str(task_id),'log',datetime.datetime.strftime(datetime.datetime.now(),'%Y-%m-%d %H:%M:%S')+ " IP:"+device['ip']+" 开始升级\n")
else:
redis_conn.hset(str(task_id),'log',str(redis_conn.hget(str(task_id),'log'))+datetime.datetime.strftime(datetime.datetime.now(),'%Y-%m-%d %H:%M:%S')+ " IP:"+device['ip']+" 开始升级\n")
return {"success":True,"msg":"正在升级中..."}
3.小结
threading模块的实现方法是用一个for循环依次start线程,这样的方法相较于线程池效率会有所降低,但是能够不阻塞主线程,在主线程结束后,子线程依然能够执行下去,能够越过资源不足的问题。
四、总结
此次实现多线程的两种方式中,主要推荐线程池的方式去执行批量的任务,但是由于我使用线程池的方法无法满足项目需求(有哪位大佬指点一下咋实现),所以换为threading模块实现。
本次也算是和Python多线程打了交道,感觉自己还是有很多东西不会,继续加油吧。