写在前面
程序效率有点低,搞了下多线程提高下效率,记录在这里。
多线程
有很多编写方式,首先推荐几个包:
- threading
- concurrent
我实用的是concurrent这个包,因为它里面有现成的线程池,只需设置并发执行的线程数即可,代码:
thread_list = []
if args.domainfile:
executor = ThreadPoolExecutor(max_workers=1000)
domain_list = process_file(args.domainfile)
# lock = threading.Lock()
for domain in domain_list:
thread_list.append(executor.submit(scrape_and_verify_scts,
domain, args.port,
args.verification_tasks,
ctlogs, basedir))
executor.shutdown(wait=True)
wait(thread_list, return_when=ALL_COMPLETED)
首先要说的是,每个线程里面的局部变量是互相不影响的(查阅了很多资料得出,但不是很确定)。所以如果有全局变量则需要上锁。总结下我遇到的几种上锁的情况:
- 打印输出数据混乱,因为并发执行的原因,所以打印需要上锁。
- 输出到文件混乱,同上。
上锁之后效率会受影响,所以我采用将每个线程写入到一个文件,后面再将文件整合的方式,目前来看整合文件也有点慢。
说一下代码中的意思:
- submit:提交一个线程到线程池
- ThreadPoolExecutor:创建线程池
- shutdown:等所有的线程结束,释放资源
- wait:主线程阻塞,等待所有线程执行完毕
包的导入: from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED, FIRST_COMPLETED
再说一下Thread重写类:
# 做一些改变,目前程序效率极低,希望采用多进程来解决这个问题,目前想法是将每个domain name看作一个进程,并行来处理。
# class ConnThread(threading.Thread):
# def __init__(self, func, domain, port, verification_tasks, logs, lock):
# threading.Thread.__init__(self)
# # super(ConnThread, self).__init__()
# self.func = func
# self.domain = domain
# self.port = port
# self.verification_tasks = verification_tasks
# self.logs = logs
# self.lock = lock
#
# def run(self):
# self.func(self.domain, self.port, self.verification_tasks, self.logs, self.lock)
将thread类重写,run里面就是你要开启的线程函数。
多进程
包:multiprocessing
用法基本和多线程一致,concurrent里面也有对应多进程的进程池。
总结
简单总结下:python中的多线程适合I/O交互比较多的程序,而多进程适合计算任务比较繁重的程序。
原因:python的GIL锁,导致多线程只能运行在一个核上,如果电脑多核那么很浪费资源。所以计算任务偏向多进程,多进程可以使用多个核进行工作,cpu可以疯狂计算。但是进程不能开太多,受计算机硬件限制,所以I/O操作更偏向于多线程。
后续
出现了新问题:程序运行一段时间后会被killed掉,原因是内存爆了,linux的保护机制将其kill。所以我们还需要考虑内存占用的问题,解决方法见:
- https://blog.youkuaiyun.com/SmallTankPy/article/details/79960739
- https://alexwlchan.net/2019/10/adventures-with-concurrent-futures/
思路:我们现在有一个100万的域名列表,然后将其每个域名看作一个线程,这样就使得其一直submit
。然后存在缓存中,最终内存爆掉。所以需要控制submit的数量。比如提交1000个然后就break
,等到少于1000个再往里提交,通过这样的方式,就可以控制缓存的占用。
还会出现提交的数量不够用的情况,这时就需要根据你设置的线程池的大小来确定该数量的大小。
我的例子:线程池:1000,提交:2000。明显感觉到速度不如以前,所以还得增加。(不是这个原因,我程序有bug,按照别人的那个方法完全可行)