python线程与线程池浅析


  众所周知,如果编码中遇到一种创建代价很大的对象,那么最好的办法就是将它池化。

  正如线程不是开得越多越好,开得多了可能导致系统的性能更低了,线程的切换会涉及cpu上下文的切换。但线程池的实现很有意思,我之前一直以为线程池就是把线程存起来,用的时候取出一个执行任务。最近深入了解concurrent.futures.Threadpoolexecutor才发现并不是那么简单。

首先摆结论:线程池核心原理是经典的生产者+消费者+消息队列中间件模型。

生产者(调用submit()方法)将任务放入消息队列

消费者(worker线程)循环从任务队列中取出任务处理

  任何线程池实现都是有个queue,生产者往queue里面submit任务,消费者是例如100个线程,每个线程里面跑的函数都是while True的死循环函数,while里面不断的用queue.get从queue里面拉取任务,拉取一个任务,就立即fun(x,y)这么去运行。任何语言任何人实现线程池一定是这个思路这么写的,没有例外。

  我之前基于threading实现的多线程装饰器也是这样,但是使用体验差了concurrent.futures十万八千里,所以人家才能成为标准库。并且它同时实现了多线程和多进程的编码接口一致,这个很难,因为多进程与多线程不一样,底层涉及fork,spawn,forkserver这些操作系统级的区别。

  扯远了,网工日常使用中还是以线程池为主,毕竟不管是paramiko登设备,还是接口调用,还是写文件都是IO操作,大部分耗时都是用在“请求之后等待回复”上面。而生产中往往会使用类似celery这种分布式函数调度框架,使用消息队列中间件来解耦生产者和消费者,主要有两点原因:

  1. python有GIL, 这里推荐一个UP主对GIL的讲解:GIL的前世今生
  2. python性能很差,不光是GIL的问题,只要是动态语言无论是否有GIL限制,都比静态语言慢很多。