├── .gitignore ├── README.md ├── dist ├── threadpool_executor_shrink_able-1.0.tar.gz ├── threadpool_executor_shrink_able-1.1.tar.gz ├── threadpool_executor_shrink_able-1.2.tar.gz └── threadpool_executor_shrink_able-1.4.tar.gz ├── nb_log_config.py ├── setup.py ├── tests ├── git_threadpool.py ├── test_auto_adjust_by_redis │ ├── consume.py │ └── push.py ├── test_threadpoolexecutor.py ├── test_threadpoolexecutor_shrinkable.py └── test_threadpoolexecutor_shrinkable_future.py ├── threadpool_executor_shrink_able.egg-info ├── PKG-INFO ├── SOURCES.txt ├── dependency_links.txt ├── requires.txt └── top_level.txt └── threadpool_executor_shrink_able ├── __init__.py ├── bounded_threadpoolexcutor.py ├── monkey_builtin_threadpoolexecutor.py ├── sharp_threadpoolexecutor.py └── sharp_threadpoolexecutor0.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea/ 3 | env_hotels/ 4 | henv/ 5 | venv/ 6 | *.pyc 7 | app/apis/logs/ 8 | app/logs/ 9 | *.log.* 10 | *.log 11 | *.lock 12 | *.pytest_cache* 13 | nohup.out 14 | apidoc/ 15 | node_modules/ 16 | hotelApi/ 17 | my_patch_frame_config0000.py 18 | my_patch_frame_config_beifen.py 19 | test_frame/my_patch_frame_config.py 20 | function_result_web/ 21 | test_frame/my/ 22 | redis_queue_web/ 23 | not_up_git/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## tips: flexible_thread_pool 2 | 3 | 另一个新版可变线程池是这个 https://github.com/ydf0509/flexible_thread_pool 4 | 5 | 6 | ## threadpool_executor_shrink_able 7 | pip install threadpool_executor_shrink_able 8 | 9 | 10 | 史上最强的python线程池。 11 | 12 | 最智能的可自动实时调节线程数量的线程池。此线程池和官方concurrent.futures的线程池 是鸭子类关系,所以可以一键替换类名 或者 import as来替换类名。 13 | 对比官方线程池,有4个创新功能或改进。 14 | 15 | 1、主要是不仅能扩大,还可自动缩小(官方内置的ThreadpoolExecutor不具备此功能,此概念是什么意思和目的,可以百度java ThreadpoolExecutor的KeepAliveTime参数的介绍), 16 | 17 | 2、非常节制的开启多线程,例如实例化一个最大100线程数目的pool,每隔2秒submit一个函数任务,而函数每次只需要1秒就能完成,实际上只需要调节增加到1个线程就可以,不需要慢慢增加到100个线程 18 | 官方的线程池不够智能,会一直增加到最大线程数目,此线程池则不会。 19 | 20 | 3、线程池任务的queue队列,修改为有界队列 21 | 22 | 4、此线程池运行函数出错时候,直接显示线程错误,官方的线程池则不会显示错误,例如函数中写1/0,任然不现实错误。 23 | 24 | 5.有线程池BoundedThreadPoolExecutor改善线程报错和有界队列。 25 | 26 | 6.patch_builtin_concurrent_futeres_threadpoolexecutor 支持给内置线程池打猴子补丁的方式,一键替换项目中所有原有的Thredpoolexecutor 27 | 28 | 7.以上是对比concurrent.futures 内置线程池,在博客园和csdn搜索 python自定义线程池这几个关键字,有上百篇博客实现线程池,但总共样子也就两三种,全部是抄袭来抄袭去,而且还很难调用,必须在程序末尾加join啥的,没有任何创意,中国博客园网友真的是很偷懒。 29 | 30 | 用法例子: 31 | 32 | ```python 33 | import time 34 | from nb_log import nb_print 35 | from threadpool_executor_shrink_able import ThreadPoolExecutorShrinkAble 36 | 37 | def f1(a): 38 | time.sleep(0.2) # 可修改这个数字测试多线程数量调节功能。 39 | 40 | nb_print(f'{a} 。。。。。。。') 41 | 42 | # raise Exception('抛个错误测试') # 官方的不会显示函数出错你,你还以为你写的代码没毛病呢。 43 | 44 | 45 | pool = ThreadPoolExecutorShrinkAble(200) 46 | 47 | # pool = ThreadPoolExecutor(200) # 测试对比官方自带 48 | 49 | for i in range(300): 50 | 51 | time.sleep(0.3) # 这里的间隔时间模拟,当任务来临不密集,只需要少量线程就能搞定f1了,因为f1的消耗时间短,不需要开那么多线程,CustomThreadPoolExecutor比ThreadPoolExecutor 优势之一。 52 | 53 | pool.submit(f1, str(i)) 54 | 55 | # 1/下面测试阻塞主线程退出的情况。注释掉可以测主线程退出的情况。 56 | 57 | # 2/此代码可以证明,在一段时间后,连续长时间没任务,官方线程池的线程数目还是保持在最大数量了。 58 | # 而此线程池会自动缩小,实现了java线程池的keppalivetime功能。 59 | 60 | time.sleep(1000000) 61 | ``` 62 | 63 | ## 对比网上线程池 64 | 1 65 | 66 | (https://www.cnblogs.com/shenwenlong/p/5604687.html) 67 | ``` 68 | 69 | #!/usr/bin/env python 70 | # -*- coding:utf-8 -*- 71 | #!/usr/bin/env python 72 | # -*- coding:utf-8 -*- 73 | 74 | import queue 75 | import threading 76 | import contextlib 77 | import time 78 | 79 | StopEvent = object() 80 | 81 | 82 | class ThreadPool(object): 83 | 84 | def __init__(self, max_num): 85 | self.q = queue.Queue()#存放任务的队列 86 | self.max_num = max_num#最大线程并发数 87 | 88 | self.terminal = False#如果为True 终止所有线程,不再获取新任务 89 | self.generate_list = [] #已经创建的线程 90 | self.free_list = []#闲置的线程 91 | 92 | def run(self, func, args, callback=None): 93 | """ 94 | 线程池执行一个任务 95 | :param func: 任务函数 96 | :param args: 任务函数所需参数 97 | :param callback: 任务执行失败或成功后执行的回调函数,回调函数有两个参数1、任务函数执行状态;2、任务函数返回值(默认为None,即:不执行回调函数) 98 | :return: 如果线程池已经终止,则返回True否则None 99 | """ 100 | 101 | if len(self.free_list) == 0 and len(self.generate_list) < self.max_num: #无空闲线程和不超过最大线程数 102 | self.generate_thread() # 创建线程 103 | w = (func, args, callback,)#保存参数为元组 104 | self.q.put(w)#添加到任务队列 105 | 106 | def generate_thread(self): 107 | """ 108 | 创建一个线程 109 | """ 110 | t = threading.Thread(target=self.call) 111 | t.start() 112 | 113 | def call(self): 114 | """ 115 | 循环去获取任务函数并执行任务函数 116 | """ 117 | current_thread = threading.currentThread#获取当前线程对象 118 | self.generate_list.append(current_thread)#添加到已创建线程里 119 | 120 | event = self.q.get() #获取任务 121 | while event != StopEvent: #如果不为停止信号 122 | 123 | func, arguments, callback = event#分别取值, 124 | try: 125 | result = func(*arguments) #运行函数,把结果赋值给result 126 | status = True #运行结果是否正常 127 | except Exception as e: 128 | status = False #不正常 129 | result = e #结果为错误信息 130 | 131 | if callback is not None: # 是否有回调函数 132 | try: 133 | callback(status, result) #执行回调函数 134 | except Exception as e: 135 | pass 136 | 137 | if self.terminal: # 默认为False ,如果调用terminal方法 138 | event = StopEvent #停止信号 139 | else: 140 | # self.free_list.append(current_thread) #执行完毕任务,添加到闲置列表 141 | # event = self.q.get() #获取任务 142 | # self.free_list.remove(current_thread) #获取到任务之后,从闲置里删除 143 | with self.worker_state(self.free_list,current_thread): 144 | event = self.q.get() 145 | 146 | 147 | else: 148 | self.generate_list.remove(current_thread) #如果收到终止信号,就从已创建的列表删除 149 | 150 | def close(self): #终止线程 151 | num = len(self.generate_list) #获取总已创建的线程 152 | while num: 153 | self.q.put(StopEvent) #添加停止信号,有几个线程就添加几个 154 | num -= 1 155 | 156 | # 终止线程(清空队列) 157 | def terminate(self): 158 | 159 | self.terminal = True #更改为True, 160 | 161 | while self.generate_list: #如果有已创建线程存活 162 | self.q.put(StopEvent) #有几个就发几个信号 163 | self.q.empty() #清空队列 164 | @contextlib.contextmanager 165 | def worker_state(self,free_list,current_thread): 166 | free_list.append(current_thread) 167 | try: 168 | yield 169 | finally: 170 | free_list.remove(current_thread) 171 | import time 172 | 173 | def work(i): 174 | print(i) 175 | 176 | pool = ThreadPool(10) 177 | for item in range(50): 178 | pool.run(func=work, args=(item,)) 179 | pool.terminate() 180 | pool.close() 181 | 182 | ``` 183 | 184 | 调用方式伤不起 185 | 186 | 主要原因是用非搜狐线程,要么程序很快结束,要么就一直while 1循环程序结束不了,造成需要这样调用。 187 | ``` 188 | pool = ThreadPool(10) 189 | 190 | for item in range(50): 191 | 192 | pool.run(func=work, args=(item,)) 193 | 194 | pool.terminate() 195 | 196 | pool.close() 197 | 198 | 199 | ``` 200 | 201 | 2 网上线程池之二 202 | 203 | (https://www.cnblogs.com/tkqasn/p/5711593.html) 204 | 205 | 仍然是采用非守护线程,导致调用方式伤不起。啥pool.close pool.join都需要,无法随时提交任务。 206 | 207 | ``` 208 | pool=ThreadPool(5) 209 | # pool.Deamon=True#需在pool.run之前设置 210 | for i in range(1000): 211 | pool.run(func=Foo,args=(i,),callback=Bar) 212 | pool.close() 213 | pool.join() 214 | # pool.terminate() 215 | ``` 216 | 217 | 218 | 源码 219 | ``` 220 | 221 | import threading 222 | import contextlib 223 | from Queue import Queue 224 | import time 225 | 226 | class ThreadPool(object): 227 | def __init__(self, max_num): 228 | self.StopEvent = 0#线程任务终止符,当线程从队列获取到StopEvent时,代表此线程可以销毁。可设置为任意与任务有区别的值。 229 | self.q = Queue() 230 | self.max_num = max_num #最大线程数 231 | self.terminal = False #是否设置线程池强制终止 232 | self.created_list = [] #已创建线程的线程列表 233 | self.free_list = [] #空闲线程的线程列表 234 | self.Deamon=False #线程是否是后台线程 235 | 236 | def run(self, func, args, callback=None): 237 | """ 238 | 线程池执行一个任务 239 | :param func: 任务函数 240 | :param args: 任务函数所需参数 241 | :param callback: 242 | :return: 如果线程池已经终止,则返回True否则None 243 | """ 244 | 245 | if len(self.free_list) == 0 and len(self.created_list) < self.max_num: 246 | self.create_thread() 247 | task = (func, args, callback,) 248 | self.q.put(task) 249 | 250 | def create_thread(self): 251 | """ 252 | 创建一个线程 253 | """ 254 | t = threading.Thread(target=self.call) 255 | t.setDaemon(self.Deamon) 256 | t.start() 257 | self.created_list.append(t)#将当前线程加入已创建线程列表created_list 258 | 259 | def call(self): 260 | """ 261 | 循环去获取任务函数并执行任务函数 262 | """ 263 | current_thread = threading.current_thread() #获取当前线程对象· 264 | event = self.q.get() #从任务队列获取任务 265 | while event != self.StopEvent: #判断获取到的任务是否是终止符 266 | 267 | func, arguments, callback = event#从任务中获取函数名、参数、和回调函数名 268 | try: 269 | result = func(*arguments) 270 | func_excute_status =True#func执行成功状态 271 | except Exception as e: 272 | func_excute_status = False 273 | result =None 274 | print '函数执行产生错误', e#打印错误信息 275 | 276 | if func_excute_status:#func执行成功后才能执行回调函数 277 | if callback is not None:#判断回调函数是否是空的 278 | try: 279 | callback(result) 280 | except Exception as e: 281 | print '回调函数执行产生错误', e # 打印错误信息 282 | 283 | 284 | with self.worker_state(self.free_list,current_thread): 285 | #执行完一次任务后,将线程加入空闲列表。然后继续去取任务,如果取到任务就将线程从空闲列表移除 286 | if self.terminal:#判断线程池终止命令,如果需要终止,则使下次取到的任务为StopEvent。 287 | event = self.StopEvent 288 | else: #否则继续获取任务 289 | event = self.q.get() # 当线程等待任务时,q.get()方法阻塞住线程,使其持续等待 290 | 291 | else:#若线程取到的任务是终止符,就销毁线程 292 | #将当前线程从已创建线程列表created_list移除 293 | self.created_list.remove(current_thread) 294 | 295 | def close(self): 296 | """ 297 | 执行完所有的任务后,所有线程停止 298 | """ 299 | full_size = len(self.created_list)#按已创建的线程数量往线程队列加入终止符。 300 | while full_size: 301 | self.q.put(self.StopEvent) 302 | full_size -= 1 303 | 304 | def terminate(self): 305 | """ 306 | 无论是否还有任务,终止线程 307 | """ 308 | self.terminal = True 309 | while self.created_list: 310 | self.q.put(self.StopEvent) 311 | 312 | self.q.queue.clear()#清空任务队列 313 | 314 | def join(self): 315 | """ 316 | 阻塞线程池上下文,使所有线程执行完后才能继续 317 | """ 318 | for t in self.created_list: 319 | t.join() 320 | 321 | 322 | @contextlib.contextmanager#上下文处理器,使其可以使用with语句修饰 323 | def worker_state(self, state_list, worker_thread): 324 | """ 325 | 用于记录线程中正在等待的线程数 326 | """ 327 | state_list.append(worker_thread) 328 | try: 329 | yield 330 | finally: 331 | state_list.remove(worker_thread) 332 | 333 | 334 | 335 | 336 | 337 | 338 | if __name__ == '__main__': 339 | def Foo(arg): 340 | return arg 341 | # time.sleep(0.1) 342 | 343 | def Bar(res): 344 | print res 345 | 346 | pool=ThreadPool(5) 347 | # pool.Deamon=True#需在pool.run之前设置 348 | for i in range(1000): 349 | pool.run(func=Foo,args=(i,),callback=Bar) 350 | pool.close() 351 | pool.join() 352 | # pool.terminate() 353 | 354 | print "任务队列里任务数%s" %pool.q.qsize() 355 | print "当前存活子线程数量:%d" % threading.activeCount() 356 | print "当前线程创建列表:%s" %pool.created_list 357 | print "当前线程创建列表:%s" %pool.free_list 358 | 359 | 详细代码, 360 | ``` 361 | 362 | 可以去博客园搜索任意自定义线程池,由于没使用守护线程实现,调用都很麻烦。 363 | 364 | 365 | ## 3 有界队列比无界队列的好处 366 | 367 | ``` 368 | 如下例子,如果用官方的原生 ThreadPoolExecutor,那么电脑会内存瞬间卡死。 369 | 如果用 BoundedThreadPoolExecutor 或者 ThreadPoolExecutorShrinkAble 则不会出现内存迅速涨满卡死电脑 370 | 371 | 因为无界队列迅速把任务添加到work_queue内存队列中。 372 | 373 | 此外无界队列的坏处包括,如果用线程池去运行消息队列中的消息,这种线程池会迅速把谁有消息从消息队列中间件里面掏空取到内存中, 374 | 破坏了负载均衡,导致有的机器没任务可消费,有的机器忙死。 375 | ``` 376 | 377 | ```python 378 | import time 379 | from concurrent.futures import ThreadPoolExecutor 380 | from threadpool_executor_shrink_able import BoundedThreadPoolExecutor 381 | 382 | 383 | # pool = ThreadPoolExecutor(10) 384 | pool = BoundedThreadPoolExecutor(10) 385 | 386 | def print_long_str(long_str): 387 | print(long_str[:10]) 388 | time.sleep(5) 389 | 390 | 391 | for i in range(10000000): 392 | pool.submit(print_long_str,'很长的字符串很占内存'*100000) 393 | 394 | 395 | ``` 396 | -------------------------------------------------------------------------------- /dist/threadpool_executor_shrink_able-1.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ydf0509/threadpool_executor_shrink_able/4d1afb27964d2ec297af072797db57c1e8101ce6/dist/threadpool_executor_shrink_able-1.0.tar.gz -------------------------------------------------------------------------------- /dist/threadpool_executor_shrink_able-1.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ydf0509/threadpool_executor_shrink_able/4d1afb27964d2ec297af072797db57c1e8101ce6/dist/threadpool_executor_shrink_able-1.1.tar.gz -------------------------------------------------------------------------------- /dist/threadpool_executor_shrink_able-1.2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ydf0509/threadpool_executor_shrink_able/4d1afb27964d2ec297af072797db57c1e8101ce6/dist/threadpool_executor_shrink_able-1.2.tar.gz -------------------------------------------------------------------------------- /dist/threadpool_executor_shrink_able-1.4.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ydf0509/threadpool_executor_shrink_able/4d1afb27964d2ec297af072797db57c1e8101ce6/dist/threadpool_executor_shrink_able-1.4.tar.gz -------------------------------------------------------------------------------- /nb_log_config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | 此文件nb_log_config.py是自动生成到python项目的根目录的。 4 | 在这里面写的变量会覆盖此文件nb_log_config_default中的值。对nb_log包进行默认的配置。 5 | 6 | 由于不同的logger天然就是多个实例,所以可以通过get_logger_and_handlers传参针对每个logger精确的做不同的配置。 7 | 最终配置方式是由get_logger_and_add_handlers方法的各种传参决定,如果方法相应的传参为None则使用这里面的配置。 8 | 最终配置方式是由get_logger_and_add_handlers方法的各种传参决定,如果方法相应的传参为None则使用这里面的配置。 9 | 最终配置方式是由get_logger_and_add_handlers方法的各种传参决定,如果方法相应的传参为None则使用这里面的配置。 10 | 重要的重复三遍。 11 | 12 | """ 13 | import logging 14 | # 15 | # DING_TALK_TOKEN = '3dd0eexxxxxadab014bd604XXXXXXXXXXXX' # 数据组报警机器人 16 | # 17 | # EMAIL_HOST = ('smtp.sohu.com', 465) 18 | # EMAIL_FROMADDR = 'aaa0509@sohu.com' # 'matafyhotel-techl@matafy.com', 19 | # EMAIL_TOADDRS = ('cccc.cheng@silknets.com', 'yan@dingtalk.com',) 20 | # EMAIL_CREDENTIALS = ('aaa0509@sohu.com', 'abcdefg') 21 | # 22 | # ELASTIC_HOST = '127.0.0.1' 23 | # ELASTIC_PORT = 9200 24 | # 25 | # KAFKA_BOOTSTRAP_SERVERS = ['192.168.199.202:9092'] 26 | # ALWAYS_ADD_KAFKA_HANDLER_IN_TEST_ENVIRONENT = False 27 | # 28 | # MONGO_URL = 'mongodb://myUserAdmin:mimamiama@127.0.0.1:27016/admin' 29 | # 30 | # DEFAULUT_USE_COLOR_HANDLER = True # 是否默认使用有彩的日志。有的人讨厌彩色可以关掉(主要是不按提示的说明配置pycahrm的conose)。 31 | # DISPLAY_BACKGROUD_COLOR_IN_CONSOLE = True # 在控制台是否显示彩色块状的日志。为False则不使用大块的背景颜色。 32 | # AUTO_PATCH_PRINT = True # 是否自动打print的猴子补丁,如果打了后指不定,print自动变色和可点击跳转。 33 | # WARNING_PYCHARM_COLOR_SETINGS = True 34 | # 35 | # DEFAULT_ADD_MULTIPROCESSING_SAFE_ROATING_FILE_HANDLER = False # 是否默认同时将日志记录到记log文件记事本中。 36 | # LOG_FILE_SIZE = 100 # 单位是M,每个文件的切片大小,超过多少后就自动切割 37 | # LOG_FILE_BACKUP_COUNT = 3 38 | # 39 | # LOG_LEVEL_FILTER = logging.DEBUG # 默认日志级别,低于此级别的日志不记录了。例如设置为INFO,那么logger.debug的不会记录,只会记录logger.info以上级别的。 40 | # RUN_ENV = 'test' 41 | # 42 | # FORMATTER_DICT = { 43 | # 1: logging.Formatter( 44 | # '日志时间【%(asctime)s】 - 日志名称【%(name)s】 - 文件【%(filename)s】 - 第【%(lineno)d】行 - 日志等级【%(levelname)s】 - 日志信息【%(message)s】', 45 | # "%Y-%m-%d %H:%M:%S"), 46 | # 2: logging.Formatter( 47 | # '%(asctime)s - %(name)s - %(filename)s - %(funcName)s - %(lineno)d - %(levelname)s - %(message)s', 48 | # "%Y-%m-%d %H:%M:%S"), 49 | # 3: logging.Formatter( 50 | # '%(asctime)s - %(name)s - 【 File "%(pathname)s", line %(lineno)d, in %(funcName)s 】 - %(levelname)s - %(message)s', 51 | # "%Y-%m-%d %H:%M:%S"), # 一个模仿traceback异常的可跳转到打印日志地方的模板 52 | # 4: logging.Formatter( 53 | # '%(asctime)s - %(name)s - "%(filename)s" - %(funcName)s - %(lineno)d - %(levelname)s - %(message)s - File "%(pathname)s", line %(lineno)d ', 54 | # "%Y-%m-%d %H:%M:%S"), # 这个也支持日志跳转 55 | # 5: logging.Formatter( 56 | # '%(asctime)s - %(name)s - "%(pathname)s:%(lineno)d" - %(funcName)s - %(levelname)s - %(message)s', 57 | # "%Y-%m-%d %H:%M:%S"), # 我认为的最好的模板,推荐 58 | # 6: logging.Formatter('%(name)s - %(asctime)-15s - %(filename)s - %(lineno)d - %(levelname)s: %(message)s', 59 | # "%Y-%m-%d %H:%M:%S"), 60 | # 7: logging.Formatter('%(levelname)s - %(filename)s - %(lineno)d - %(message)s'), # 一个只显示简短文件名和所处行数的日志模板 61 | # } 62 | # 63 | # FORMATTER_KIND = 5 # 默认选择第几个模板 64 | 65 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from pathlib import Path 3 | from setuptools import setup, find_packages 4 | 5 | # with open("README.md", "r",encoding='utf8') as fh: 6 | # long_description = fh.read() 7 | 8 | # filepath = ((Path(__file__).parent / Path('README.md')).absolute()).as_posix() 9 | filepath = 'README.md' 10 | print(filepath) 11 | 12 | setup( 13 | name='threadpool_executor_shrink_able', # 14 | version="1.4", 15 | description=( 'shap threadpoolexecutor, realize java keepAliveTime,bounded work queue,direct display of thread errors '), 16 | keywords=("threadpool", "threadpoolexecutor", "thread shrink", ), 17 | # long_description=open('README.md', 'r',encoding='utf8').read(), 18 | long_description_content_type="text/markdown", 19 | long_description= open(filepath, 'r',encoding='utf8').read(), 20 | # data_files=[filepath], 21 | author='bfzs', 22 | author_email='ydf0509@sohu.com', 23 | maintainer='ydf', 24 | maintainer_email='ydf0509@sohu.com', 25 | license='BSD License', 26 | packages=find_packages(), 27 | include_package_data=True, 28 | platforms=["all"], 29 | url='', 30 | classifiers=[ 31 | 'Development Status :: 4 - Beta', 32 | 'Operating System :: OS Independent', 33 | 'Intended Audience :: Developers', 34 | 'License :: OSI Approved :: BSD License', 35 | 'Programming Language :: Python', 36 | 'Programming Language :: Python :: Implementation', 37 | 'Programming Language :: Python :: 3.6', 38 | 'Topic :: Software Development :: Libraries' 39 | ], 40 | install_requires=[ 41 | 'nb_log' 42 | ] 43 | ) 44 | """ 45 | 打包上传 46 | python setup.py sdist upload -r pypi 47 | 48 | 49 | python setup.py sdist & twine upload dist/threadpool_executor_shrink_able-1.4.tar.gz 50 | twine upload dist/* 51 | 52 | 53 | python -m pip install threadpool_executor_shrink_able --upgrade -i https://pypi.org/simple # 及时的方式,不用等待 阿里云 豆瓣 同步 54 | """ 55 | -------------------------------------------------------------------------------- /tests/git_threadpool.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import subprocess 3 | import os 4 | import time 5 | 6 | # os.environ["path"] = r'C:\Program Files\Git\mingw64\libexec\git-core' 7 | def getstatusoutput(cmd): 8 | try: 9 | data = subprocess.check_output(cmd, shell=True, universal_newlines=True, 10 | stderr=subprocess.STDOUT, encoding='utf8') 11 | exitcode = 0 12 | except subprocess.CalledProcessError as ex: 13 | data = ex.output 14 | exitcode = ex.returncode 15 | if data[-1:] == '\n': 16 | data = data[:-1] 17 | return exitcode, data 18 | 19 | 20 | def do_cmd(cmd_strx): 21 | print(f'执行 {cmd_strx}') 22 | retx = getstatusoutput(cmd_strx) 23 | print(retx[0]) 24 | # if retx[0] !=0: 25 | # raise ValueError('要检查git提交') 26 | print(retx[1], '\n') 27 | return retx 28 | 29 | 30 | t0 = time.time() 31 | 32 | do_cmd('git pull') 33 | 34 | do_cmd('git diff') 35 | 36 | do_cmd('git add ../.') 37 | 38 | do_cmd('git commit -m commit') 39 | 40 | do_cmd('git push origin') 41 | 42 | # print(subprocess.getstatusoutput('git push github')) 43 | print(f'{time.strftime("%H:%M:%S")} spend_time {time.time() - t0}') 44 | time.sleep(100000) 45 | 46 | '''dsds''' -------------------------------------------------------------------------------- /tests/test_auto_adjust_by_redis/consume.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from redis import Redis 4 | from threadpool_executor_shrink_able import ThreadPoolExecutorShrinkAble 5 | from threadpool_executor_shrink_able.sharp_threadpoolexecutor import set_thread_pool_executor_shrinkable,show_current_threads_num 6 | 7 | """ 8 | 引入redis在不同时候慢慢放入任务 和隔很久加入孺人,这样才方便测试 线程池自动扩张和减小。 9 | """ 10 | 11 | r = Redis(decode_responses=True) 12 | 13 | 14 | 15 | pool = ThreadPoolExecutorShrinkAble(100) 16 | set_thread_pool_executor_shrinkable(2, 10) 17 | print(pool.MIN_WORKERS, pool.KEEP_ALIVE_TIME) 18 | 19 | show_current_threads_num(1) 20 | 21 | def f(x): 22 | time.sleep(0.2) 23 | # y = ['我' * 1000 * 1000 *100] 24 | y = '我' * 1000 * 1000 * 100 25 | print(x) 26 | 27 | 28 | while 1: 29 | xx = r.blpop('test_adjust_task') 30 | pool.submit(f, xx[1]) 31 | -------------------------------------------------------------------------------- /tests/test_auto_adjust_by_redis/push.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from redis import Redis 4 | 5 | 6 | r = Redis() 7 | 8 | 9 | for j in range(100): 10 | for i in range(10): 11 | print('推送', i + j) 12 | r.lpush('test_adjust_task',i+j) 13 | time.sleep(16) # 测试 12 和 3 14 | 15 | -------------------------------------------------------------------------------- /tests/test_threadpoolexecutor.py: -------------------------------------------------------------------------------- 1 | import time 2 | from concurrent.futures import ThreadPoolExecutor 3 | import nb_log 4 | from threadpool_executor_shrink_able import show_current_threads_num 5 | 6 | show_current_threads_num(sleep_time=5) 7 | 8 | 9 | def f1(a): 10 | time.sleep(0.2) # 可修改这个数字测试多线程数量调节功能。 11 | b = [666] * 1000 * 10000 12 | print(f'{a} 。。。。。。。') 13 | # raise Exception('抛个错误测试') # 官方的不会显示函数出错你,你还以为你写的代码没毛病呢。 14 | 15 | 16 | # pool = ThreadPoolExecutorShrinkAble(200) 17 | pool = ThreadPoolExecutor(200) # 测试对比官方自带 18 | 19 | for i in range(300): 20 | time.sleep(1) # 这里的间隔时间模拟,当任务来临不密集,只需要少量线程就能搞定f1了,因为f1的消耗时间短, 21 | # 不需要开那么多线程,CustomThreadPoolExecutor比ThreadPoolExecutor 优势之一。 22 | pool.submit(f1, str(i)) 23 | 24 | # 1/下面测试阻塞主线程退出的情况。注释掉可以测主线程退出的情况。 25 | # 2/此代码可以证明,在一段时间后,连续长时间没任务,官方线程池的线程数目还是保持在最大数量了。而此线程池会自动缩小,实现了java线程池的keppalivetime功能。 26 | time.sleep(1000000) 27 | -------------------------------------------------------------------------------- /tests/test_threadpoolexecutor_shrinkable.py: -------------------------------------------------------------------------------- 1 | import time 2 | from concurrent.futures import ThreadPoolExecutor 3 | import nb_log 4 | from threadpool_executor_shrink_able import show_current_threads_num, ThreadPoolExecutorShrinkAble 5 | 6 | show_current_threads_num(sleep_time=5) 7 | 8 | 9 | def f1(a): 10 | time.sleep(0.2) # 可修改这个数字测试多线程数量调节功能。 11 | b = [666] * 1000 * 10000 12 | print(f'{a} 。。。。。。。') 13 | # raise Exception('抛个错误测试') # 官方的不会显示函数出错你,你还以为你写的代码没毛病呢。 14 | 15 | 16 | pool = ThreadPoolExecutorShrinkAble(200) 17 | 18 | 19 | for i in range(300): 20 | time.sleep(1) # 这里的间隔时间模拟,当任务来临不密集,只需要少量线程就能搞定f1了,因为f1的消耗时间短, 21 | # 不需要开那么多线程,CustomThreadPoolExecutor比ThreadPoolExecutor 优势之一。 22 | pool.submit(f1, str(i)) 23 | 24 | # 1/下面测试阻塞主线程退出的情况。注释掉可以测主线程退出的情况。 25 | # 2/此代码可以证明,在一段时间后,连续长时间没任务,官方线程池的线程数目还是保持在最大数量了。而此线程池会自动缩小,实现了java线程池的keppalivetime功能。 26 | time.sleep(1000000) 27 | -------------------------------------------------------------------------------- /tests/test_threadpoolexecutor_shrinkable_future.py: -------------------------------------------------------------------------------- 1 | import time 2 | from concurrent.futures import ThreadPoolExecutor 3 | import nb_log 4 | from threadpool_executor_shrink_able.sharp_threadpoolexecutor2 import show_current_threads_num, ThreadPoolExecutorShrinkAble 5 | 6 | show_current_threads_num(sleep_time=5) 7 | 8 | 9 | def f1(a): 10 | time.sleep(0.2) # 可修改这个数字测试多线程数量调节功能。 11 | # b = [666] * 1000 * 10000 12 | print(f'{a} 。。。。。。。') 13 | # raise Exception('抛个错误测试') # 官方的不会显示函数出错你,你还以为你写的代码没毛病呢。 14 | return a * 10 15 | 16 | 17 | pool = ThreadPoolExecutorShrinkAble(20) 18 | 19 | 20 | futures = pool.map(f1,[i for i in range(100)]) 21 | for fut in futures: 22 | print(fut) 23 | 24 | time.sleep(1000000) 25 | -------------------------------------------------------------------------------- /threadpool_executor_shrink_able.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: threadpool-executor-shrink-able 3 | Version: 1.4 4 | Summary: shap threadpoolexecutor, realize java keepAliveTime,bounded work queue,direct display of thread errors 5 | Home-page: UNKNOWN 6 | Author: bfzs 7 | Author-email: ydf0509@sohu.com 8 | Maintainer: ydf 9 | Maintainer-email: ydf0509@sohu.com 10 | License: BSD License 11 | Description: pip install threadpool_executor_shrink_able 12 | 13 | 14 | 15 | 16 | 17 | 史上最强的python线程池。 18 | 19 | 最智能的可自动实时调节线程数量的线程池。此线程池和官方concurrent.futures的线程池 是鸭子类关系,所以可以一键替换类名 或者 import as来替换类名。 20 | 对比官方线程池,有4个创新功能或改进。 21 | 22 | 1、主要是不仅能扩大,还可自动缩小(官方内置的ThreadpoolExecutor不具备此功能,此概念是什么意思和目的,可以百度java ThreadpoolExecutor的KeepAliveTime参数的介绍), 23 | 24 | 2、非常节制的开启多线程,例如实例化一个最大100线程数目的pool,每隔2秒submit一个函数任务,而函数每次只需要1秒就能完成,实际上只需要调节增加到1个线程就可以,不需要慢慢增加到100个线程 25 | 官方的线程池不够智能,会一直增加到最大线程数目,此线程池则不会。 26 | 27 | 3、线程池任务的queue队列,修改为有界队列 28 | 29 | 4、此线程池运行函数出错时候,直接显示线程错误,官方的线程池则不会显示错误,例如函数中写1/0,任然不现实错误。 30 | 31 | 5.有线程池BoundedThreadPoolExecutor改善线程报错和有界队列。 32 | 33 | 6.patch_builtin_concurrent_futeres_threadpoolexecutor 支持给内置线程池打猴子补丁的方式,一键替换项目中所有原有的Thredpoolexecutor 34 | 35 | 7.以上是对比concurrent.futures 内置线程池,在博客园和csdn搜索 python自定义线程池这几个关键字,有上百篇博客实现线程池,但总共样子也就两三种,全部是抄袭来抄袭去,而且还很难调用,必须在程序末尾加join啥的,没有任何创意,中国程序员真的是很差劲。 36 | 37 | 用法例子: 38 | 39 | ```python 40 | import time 41 | from nb_log import nb_print 42 | from threadpool_executor_shrink_able import ThreadPoolExecutorShrinkAble 43 | 44 | def f1(a): 45 | time.sleep(0.2) # 可修改这个数字测试多线程数量调节功能。 46 | 47 | nb_print(f'{a} 。。。。。。。') 48 | 49 | # raise Exception('抛个错误测试') # 官方的不会显示函数出错你,你还以为你写的代码没毛病呢。 50 | 51 | 52 | pool = ThreadPoolExecutorShrinkAble(200) 53 | 54 | # pool = ThreadPoolExecutor(200) # 测试对比官方自带 55 | 56 | for i in range(300): 57 | 58 | time.sleep(0.3) # 这里的间隔时间模拟,当任务来临不密集,只需要少量线程就能搞定f1了,因为f1的消耗时间短,不需要开那么多线程,CustomThreadPoolExecutor比ThreadPoolExecutor 优势之一。 59 | 60 | pool.submit(f1, str(i)) 61 | 62 | # 1/下面测试阻塞主线程退出的情况。注释掉可以测主线程退出的情况。 63 | 64 | # 2/此代码可以证明,在一段时间后,连续长时间没任务,官方线程池的线程数目还是保持在最大数量了。 65 | # 而此线程池会自动缩小,实现了java线程池的keppalivetime功能。 66 | 67 | time.sleep(1000000) 68 | ``` 69 | 70 | ## 对比网上线程池 71 | 1 72 | 73 | (https://www.cnblogs.com/shenwenlong/p/5604687.html) 74 | ``` 75 | 76 | #!/usr/bin/env python 77 | # -*- coding:utf-8 -*- 78 | #!/usr/bin/env python 79 | # -*- coding:utf-8 -*- 80 | 81 | import queue 82 | import threading 83 | import contextlib 84 | import time 85 | 86 | StopEvent = object() 87 | 88 | 89 | class ThreadPool(object): 90 | 91 | def __init__(self, max_num): 92 | self.q = queue.Queue()#存放任务的队列 93 | self.max_num = max_num#最大线程并发数 94 | 95 | self.terminal = False#如果为True 终止所有线程,不再获取新任务 96 | self.generate_list = [] #已经创建的线程 97 | self.free_list = []#闲置的线程 98 | 99 | def run(self, func, args, callback=None): 100 | """ 101 | 线程池执行一个任务 102 | :param func: 任务函数 103 | :param args: 任务函数所需参数 104 | :param callback: 任务执行失败或成功后执行的回调函数,回调函数有两个参数1、任务函数执行状态;2、任务函数返回值(默认为None,即:不执行回调函数) 105 | :return: 如果线程池已经终止,则返回True否则None 106 | """ 107 | 108 | if len(self.free_list) == 0 and len(self.generate_list) < self.max_num: #无空闲线程和不超过最大线程数 109 | self.generate_thread() # 创建线程 110 | w = (func, args, callback,)#保存参数为元组 111 | self.q.put(w)#添加到任务队列 112 | 113 | def generate_thread(self): 114 | """ 115 | 创建一个线程 116 | """ 117 | t = threading.Thread(target=self.call) 118 | t.start() 119 | 120 | def call(self): 121 | """ 122 | 循环去获取任务函数并执行任务函数 123 | """ 124 | current_thread = threading.currentThread#获取当前线程对象 125 | self.generate_list.append(current_thread)#添加到已创建线程里 126 | 127 | event = self.q.get() #获取任务 128 | while event != StopEvent: #如果不为停止信号 129 | 130 | func, arguments, callback = event#分别取值, 131 | try: 132 | result = func(*arguments) #运行函数,把结果赋值给result 133 | status = True #运行结果是否正常 134 | except Exception as e: 135 | status = False #不正常 136 | result = e #结果为错误信息 137 | 138 | if callback is not None: # 是否有回调函数 139 | try: 140 | callback(status, result) #执行回调函数 141 | except Exception as e: 142 | pass 143 | 144 | if self.terminal: # 默认为False ,如果调用terminal方法 145 | event = StopEvent #停止信号 146 | else: 147 | # self.free_list.append(current_thread) #执行完毕任务,添加到闲置列表 148 | # event = self.q.get() #获取任务 149 | # self.free_list.remove(current_thread) #获取到任务之后,从闲置里删除 150 | with self.worker_state(self.free_list,current_thread): 151 | event = self.q.get() 152 | 153 | 154 | else: 155 | self.generate_list.remove(current_thread) #如果收到终止信号,就从已创建的列表删除 156 | 157 | def close(self): #终止线程 158 | num = len(self.generate_list) #获取总已创建的线程 159 | while num: 160 | self.q.put(StopEvent) #添加停止信号,有几个线程就添加几个 161 | num -= 1 162 | 163 | # 终止线程(清空队列) 164 | def terminate(self): 165 | 166 | self.terminal = True #更改为True, 167 | 168 | while self.generate_list: #如果有已创建线程存活 169 | self.q.put(StopEvent) #有几个就发几个信号 170 | self.q.empty() #清空队列 171 | @contextlib.contextmanager 172 | def worker_state(self,free_list,current_thread): 173 | free_list.append(current_thread) 174 | try: 175 | yield 176 | finally: 177 | free_list.remove(current_thread) 178 | import time 179 | 180 | def work(i): 181 | print(i) 182 | 183 | pool = ThreadPool(10) 184 | for item in range(50): 185 | pool.run(func=work, args=(item,)) 186 | pool.terminate() 187 | pool.close() 188 | 189 | ``` 190 | 191 | 调用方式伤不起 192 | 193 | 主要原因是用非搜狐线程,要么程序很快结束,要么就一直while 1循环程序结束不了,造成需要这样调用。 194 | ``` 195 | pool = ThreadPool(10) 196 | 197 | for item in range(50): 198 | 199 | pool.run(func=work, args=(item,)) 200 | 201 | pool.terminate() 202 | 203 | pool.close() 204 | 205 | 206 | ``` 207 | 208 | 2 网上线程池之二 209 | 210 | (https://www.cnblogs.com/tkqasn/p/5711593.html) 211 | 212 | 仍然是采用非守护线程,导致调用方式伤不起。啥pool.close pool.join都需要,无法随时提交任务。 213 | 214 | ``` 215 | pool=ThreadPool(5) 216 | # pool.Deamon=True#需在pool.run之前设置 217 | for i in range(1000): 218 | pool.run(func=Foo,args=(i,),callback=Bar) 219 | pool.close() 220 | pool.join() 221 | # pool.terminate() 222 | ``` 223 | 224 | 225 | 源码 226 | ``` 227 | 228 | import threading 229 | import contextlib 230 | from Queue import Queue 231 | import time 232 | 233 | class ThreadPool(object): 234 | def __init__(self, max_num): 235 | self.StopEvent = 0#线程任务终止符,当线程从队列获取到StopEvent时,代表此线程可以销毁。可设置为任意与任务有区别的值。 236 | self.q = Queue() 237 | self.max_num = max_num #最大线程数 238 | self.terminal = False #是否设置线程池强制终止 239 | self.created_list = [] #已创建线程的线程列表 240 | self.free_list = [] #空闲线程的线程列表 241 | self.Deamon=False #线程是否是后台线程 242 | 243 | def run(self, func, args, callback=None): 244 | """ 245 | 线程池执行一个任务 246 | :param func: 任务函数 247 | :param args: 任务函数所需参数 248 | :param callback: 249 | :return: 如果线程池已经终止,则返回True否则None 250 | """ 251 | 252 | if len(self.free_list) == 0 and len(self.created_list) < self.max_num: 253 | self.create_thread() 254 | task = (func, args, callback,) 255 | self.q.put(task) 256 | 257 | def create_thread(self): 258 | """ 259 | 创建一个线程 260 | """ 261 | t = threading.Thread(target=self.call) 262 | t.setDaemon(self.Deamon) 263 | t.start() 264 | self.created_list.append(t)#将当前线程加入已创建线程列表created_list 265 | 266 | def call(self): 267 | """ 268 | 循环去获取任务函数并执行任务函数 269 | """ 270 | current_thread = threading.current_thread() #获取当前线程对象· 271 | event = self.q.get() #从任务队列获取任务 272 | while event != self.StopEvent: #判断获取到的任务是否是终止符 273 | 274 | func, arguments, callback = event#从任务中获取函数名、参数、和回调函数名 275 | try: 276 | result = func(*arguments) 277 | func_excute_status =True#func执行成功状态 278 | except Exception as e: 279 | func_excute_status = False 280 | result =None 281 | print '函数执行产生错误', e#打印错误信息 282 | 283 | if func_excute_status:#func执行成功后才能执行回调函数 284 | if callback is not None:#判断回调函数是否是空的 285 | try: 286 | callback(result) 287 | except Exception as e: 288 | print '回调函数执行产生错误', e # 打印错误信息 289 | 290 | 291 | with self.worker_state(self.free_list,current_thread): 292 | #执行完一次任务后,将线程加入空闲列表。然后继续去取任务,如果取到任务就将线程从空闲列表移除 293 | if self.terminal:#判断线程池终止命令,如果需要终止,则使下次取到的任务为StopEvent。 294 | event = self.StopEvent 295 | else: #否则继续获取任务 296 | event = self.q.get() # 当线程等待任务时,q.get()方法阻塞住线程,使其持续等待 297 | 298 | else:#若线程取到的任务是终止符,就销毁线程 299 | #将当前线程从已创建线程列表created_list移除 300 | self.created_list.remove(current_thread) 301 | 302 | def close(self): 303 | """ 304 | 执行完所有的任务后,所有线程停止 305 | """ 306 | full_size = len(self.created_list)#按已创建的线程数量往线程队列加入终止符。 307 | while full_size: 308 | self.q.put(self.StopEvent) 309 | full_size -= 1 310 | 311 | def terminate(self): 312 | """ 313 | 无论是否还有任务,终止线程 314 | """ 315 | self.terminal = True 316 | while self.created_list: 317 | self.q.put(self.StopEvent) 318 | 319 | self.q.queue.clear()#清空任务队列 320 | 321 | def join(self): 322 | """ 323 | 阻塞线程池上下文,使所有线程执行完后才能继续 324 | """ 325 | for t in self.created_list: 326 | t.join() 327 | 328 | 329 | @contextlib.contextmanager#上下文处理器,使其可以使用with语句修饰 330 | def worker_state(self, state_list, worker_thread): 331 | """ 332 | 用于记录线程中正在等待的线程数 333 | """ 334 | state_list.append(worker_thread) 335 | try: 336 | yield 337 | finally: 338 | state_list.remove(worker_thread) 339 | 340 | 341 | 342 | 343 | 344 | 345 | if __name__ == '__main__': 346 | def Foo(arg): 347 | return arg 348 | # time.sleep(0.1) 349 | 350 | def Bar(res): 351 | print res 352 | 353 | pool=ThreadPool(5) 354 | # pool.Deamon=True#需在pool.run之前设置 355 | for i in range(1000): 356 | pool.run(func=Foo,args=(i,),callback=Bar) 357 | pool.close() 358 | pool.join() 359 | # pool.terminate() 360 | 361 | print "任务队列里任务数%s" %pool.q.qsize() 362 | print "当前存活子线程数量:%d" % threading.activeCount() 363 | print "当前线程创建列表:%s" %pool.created_list 364 | print "当前线程创建列表:%s" %pool.free_list 365 | 366 | 详细代码 367 | ``` 368 | 369 | 可以去博客园搜索任意自定义线程池,由于没使用守护线程实现,调用都很麻烦。 370 | 371 | Keywords: threadpool,threadpoolexecutor,thread shrink 372 | Platform: all 373 | Classifier: Development Status :: 4 - Beta 374 | Classifier: Operating System :: OS Independent 375 | Classifier: Intended Audience :: Developers 376 | Classifier: License :: OSI Approved :: BSD License 377 | Classifier: Programming Language :: Python 378 | Classifier: Programming Language :: Python :: Implementation 379 | Classifier: Programming Language :: Python :: 3.6 380 | Classifier: Topic :: Software Development :: Libraries 381 | Description-Content-Type: text/markdown 382 | -------------------------------------------------------------------------------- /threadpool_executor_shrink_able.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | README.md 2 | setup.py 3 | threadpool_executor_shrink_able/__init__.py 4 | threadpool_executor_shrink_able/bounded_threadpoolexcutor.py 5 | threadpool_executor_shrink_able/monkey_builtin_threadpoolexecutor.py 6 | threadpool_executor_shrink_able/sharp_threadpoolexecutor.py 7 | threadpool_executor_shrink_able/sharp_threadpoolexecutor0.py 8 | threadpool_executor_shrink_able.egg-info/PKG-INFO 9 | threadpool_executor_shrink_able.egg-info/SOURCES.txt 10 | threadpool_executor_shrink_able.egg-info/dependency_links.txt 11 | threadpool_executor_shrink_able.egg-info/requires.txt 12 | threadpool_executor_shrink_able.egg-info/top_level.txt -------------------------------------------------------------------------------- /threadpool_executor_shrink_able.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /threadpool_executor_shrink_able.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | nb_log 2 | -------------------------------------------------------------------------------- /threadpool_executor_shrink_able.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | threadpool_executor_shrink_able 2 | -------------------------------------------------------------------------------- /threadpool_executor_shrink_able/__init__.py: -------------------------------------------------------------------------------- 1 | from .sharp_threadpoolexecutor import CustomThreadpoolExecutor,ThreadPoolExecutorShrinkAble,show_current_threads_num 2 | from .bounded_threadpoolexcutor import BoundedThreadPoolExecutor 3 | from .monkey_builtin_threadpoolexecutor import patch_builtin_concurrent_futeres_threadpoolexecutor 4 | -------------------------------------------------------------------------------- /threadpool_executor_shrink_able/bounded_threadpoolexcutor.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 一个有界任务队列的thradpoolexcutor 4 | 直接捕获错误日志 5 | """ 6 | from functools import wraps 7 | import queue 8 | from concurrent.futures import ThreadPoolExecutor 9 | # noinspection PyProtectedMember 10 | # from concurrent.futures.thread import _WorkItem 11 | from nb_log import LogManager 12 | 13 | logger = LogManager('BoundedThreadPoolExecutor').get_logger_and_add_handlers() 14 | 15 | 16 | def _deco(f): 17 | @wraps(f) 18 | def __deco(*args, **kwargs): 19 | try: 20 | return f(*args, **kwargs) 21 | except Exception as e: 22 | logger.exception(e) 23 | 24 | return __deco 25 | 26 | 27 | class BoundedThreadPoolExecutor(ThreadPoolExecutor, ): 28 | def __init__(self, max_workers=None, thread_name_prefix=''): 29 | ThreadPoolExecutor.__init__(self, max_workers, thread_name_prefix) 30 | self._work_queue = queue.Queue(max_workers * 2) 31 | 32 | def submit(self, fn, *args, **kwargs): 33 | fn_deco = _deco(fn) 34 | super().submit(fn_deco, *args, **kwargs) 35 | # with self._shutdown_lock: 36 | # if self._shutdown: 37 | # raise RuntimeError('cannot schedule new futures after shutdown') 38 | # f = Future() 39 | # fn_deco = _deco(fn) 40 | # w = _WorkItem(f, fn_deco, args, kwargs) 41 | # self._work_queue.put(w) 42 | # self._adjust_thread_count() 43 | # return f 44 | 45 | 46 | if __name__ == '__main__': 47 | def fun(): 48 | print(1 / 0) 49 | 50 | pool = BoundedThreadPoolExecutor(10) 51 | pool.submit(fun) 52 | -------------------------------------------------------------------------------- /threadpool_executor_shrink_able/monkey_builtin_threadpoolexecutor.py: -------------------------------------------------------------------------------- 1 | """ 2 | 这个主要是为了 1/防止内置线程池,线程中的函数出错了不报错,导致以为代码没毛病。 3 | 2/有界队列 4 | 5 | """ 6 | import concurrent 7 | from threadpool_executor_shrink_able.bounded_threadpoolexcutor import BoundedThreadPoolExecutor 8 | 9 | 10 | def patch_builtin_concurrent_futeres_threadpoolexecutor(): 11 | concurrent.futures.ThreadPoolExecutor = BoundedThreadPoolExecutor 12 | 13 | 14 | if __name__ == '__main__': 15 | patch_builtin_concurrent_futeres_threadpoolexecutor() # 如果不大猴子补丁,出错了自己完全不知道。 16 | from concurrent.futures import ThreadPoolExecutor 17 | 18 | def test_error(): 19 | raise ValueError('测试错误') 20 | 21 | pool = ThreadPoolExecutor(20) 22 | pool.submit(test_error) 23 | -------------------------------------------------------------------------------- /threadpool_executor_shrink_able/sharp_threadpoolexecutor.py: -------------------------------------------------------------------------------- 1 | """ 2 | 史上最强的python线程池。 3 | 4 | 最智能的可自动实时调节线程数量的线程池。此线程池和官方concurrent.futures的线程池 是鸭子类关系,所以可以一键替换类名 或者 import as来替换类名。 5 | 对比官方线程池,有4个创新功能或改进。 6 | 7 | 1、主要是不仅能扩大,还可自动缩小(官方内置的ThreadpoolExecutor不具备此功能,此概念是什么意思和目的,可以百度java ThreadpoolExecutor的KeepAliveTime参数的介绍), 8 | 9 | 2、非常节制的开启多线程,例如实例化一个最大100线程数目的pool,每隔2秒submit一个函数任务,而函数每次只需要1秒就能完成,实际上只需要调节增加到1个线程就可以,不需要慢慢增加到100个线程 10 | 官方的线程池不够智能,会一直增加到最大线程数目,此线程池则不会。 11 | 12 | 3、线程池任务的queue队列,修改为有界队列 13 | 14 | 4、此线程池运行函数出错时候,直接显示线程错误,官方的线程池则不会显示错误,例如函数中写1/0,任然不现实错误。 15 | 16 | 这是完整版实现 ThreadpoolExecutor ,支持future相关的功能 17 | """ 18 | import os 19 | import atexit 20 | import queue 21 | import sys 22 | import threading 23 | import time 24 | import weakref 25 | from nb_log import LoggerMixin, nb_print, LoggerLevelSetterMixin, LogManager 26 | from concurrent.futures import Executor, Future 27 | 28 | _shutdown = False 29 | _threads_queues = weakref.WeakKeyDictionary() 30 | 31 | 32 | def _python_exit(): 33 | global _shutdown 34 | _shutdown = True 35 | items = list(_threads_queues.items()) 36 | for t, q in items: 37 | q.put(None) 38 | for t, q in items: 39 | t.join() 40 | 41 | 42 | atexit.register(_python_exit) 43 | 44 | 45 | class _WorkItem(LoggerMixin): 46 | def __init__(self, future, fn, args, kwargs): 47 | self.future = future 48 | self.fn = fn 49 | self.args = args 50 | self.kwargs = kwargs 51 | 52 | def run(self): 53 | # noinspection PyBroadException 54 | if not self.future.set_running_or_notify_cancel(): 55 | return 56 | try: 57 | result = self.fn(*self.args, **self.kwargs) 58 | except BaseException as exc: 59 | self.logger.exception(f'函数 {self.fn.__name__} 中发生错误,错误原因是 {type(exc)} {exc} ') 60 | self.future.set_exception(exc) 61 | # Break a reference cycle with the exception 'exc' 62 | self = None 63 | else: 64 | self.future.set_result(result) 65 | 66 | def __str__(self): 67 | return f'{(self.fn.__name__, self.args, self.kwargs)}' 68 | 69 | 70 | def set_thread_pool_executor_shrinkable(min_works, keep_alive_time): 71 | ThreadPoolExecutorShrinkAble.MIN_WORKERS = min_works 72 | ThreadPoolExecutorShrinkAble.KEEP_ALIVE_TIME = keep_alive_time 73 | 74 | 75 | class ThreadPoolExecutorShrinkAble(Executor, LoggerMixin, LoggerLevelSetterMixin): 76 | # 为了和官方自带的THredpoolexecutor保持完全一致的鸭子类,参数设置成死的,不让用户传参了。建议用猴子补丁修改这里的两个常量。 77 | # MIN_WORKERS = 5 # 最小值可以设置为0 78 | # KEEP_ALIVE_TIME = 60 79 | 80 | MIN_WORKERS = 1 81 | KEEP_ALIVE_TIME = 10 82 | 83 | def __init__(self, max_workers:int=None, thread_name_prefix=''): 84 | """ 85 | 最好需要兼容官方concurren.futures.ThreadPoolExecutor 和改版的BoundedThreadPoolExecutor,入参名字和个数保持了一致。 86 | :param max_workers: 87 | :param thread_name_prefix: 88 | """ 89 | self._max_workers = max_workers or 4 90 | self._thread_name_prefix = thread_name_prefix 91 | self.work_queue = self._work_queue = queue.Queue(max_workers or 10) 92 | # self._threads = set() 93 | self._threads = weakref.WeakSet() 94 | self._lock_compute_threads_free_count = threading.Lock() 95 | self.threads_free_count = 0 96 | self._shutdown = False 97 | self._shutdown_lock = threading.Lock() 98 | 99 | def _change_threads_free_count(self, change_num): 100 | with self._lock_compute_threads_free_count: 101 | self.threads_free_count += change_num 102 | 103 | def submit(self, func, *args, **kwargs): 104 | with self._shutdown_lock: 105 | if self._shutdown: 106 | raise RuntimeError('不能添加新的任务到线程池') 107 | f = Future() 108 | w = _WorkItem(f, func, args, kwargs) 109 | self.work_queue.put(w) 110 | self._adjust_thread_count() 111 | return f 112 | 113 | def _adjust_thread_count(self): 114 | # print(self.threads_free_count, self.MIN_WORKERS, len(self._threads), self._max_workers) 115 | if self.threads_free_count <= self.MIN_WORKERS and len(self._threads) < self._max_workers: 116 | t = _CustomThread(self).set_log_level(self.logger.level) 117 | t.daemon = True 118 | t.start() 119 | self._threads.add(t) 120 | _threads_queues[t] = self._work_queue 121 | 122 | def shutdown(self, wait=True): # noqa 123 | with self._shutdown_lock: 124 | self._shutdown = True 125 | self.work_queue.put(None) 126 | if wait: 127 | for t in self._threads: 128 | t.join() 129 | 130 | 131 | # 两个名字都可以,兼容以前的老名字(中文意思是 自定义线程池),但新名字更能表达意思(可缩小线程池)。 132 | CustomThreadpoolExecutor = CustomThreadPoolExecutor = ThreadPoolExecutorShrinkAble 133 | 134 | 135 | # noinspection PyProtectedMember 136 | class _CustomThread(threading.Thread, LoggerMixin, LoggerLevelSetterMixin): 137 | _lock_for_judge_threads_free_count = threading.Lock() 138 | 139 | def __init__(self, executorx: ThreadPoolExecutorShrinkAble): 140 | super().__init__() 141 | self._executorx = executorx 142 | 143 | def _remove_thread(self, stop_resson=''): 144 | # noinspection PyUnresolvedReferences 145 | self.logger.debug(f'停止线程 {self._ident}, 触发条件是 {stop_resson} ') 146 | self._executorx._change_threads_free_count(-1) 147 | self._executorx._threads.remove(self) 148 | _threads_queues.pop(self) 149 | 150 | # noinspection PyProtectedMember 151 | def run(self): 152 | # noinspection PyUnresolvedReferences 153 | self.logger.debug(f'新启动线程 {self._ident} ') 154 | self._executorx._change_threads_free_count(1) 155 | while True: 156 | try: 157 | work_item = self._executorx.work_queue.get(block=True, timeout=self._executorx.KEEP_ALIVE_TIME) 158 | except queue.Empty: 159 | # continue 160 | # self._remove_thread() 161 | with self._lock_for_judge_threads_free_count: 162 | if self._executorx.threads_free_count > self._executorx.MIN_WORKERS: 163 | self._remove_thread( 164 | f'当前线程超过 {self._executorx.KEEP_ALIVE_TIME} 秒没有任务,线程池中不在工作状态中的线程数量是 ' 165 | f'{self._executorx.threads_free_count},超过了指定的最小核心数量 {self._executorx.MIN_WORKERS}') 166 | break # 退出while 1,即是结束。这里才是决定线程结束销毁,_remove_thread只是个名字而已,不是由那个来销毁线程。 167 | else: 168 | continue 169 | 170 | if work_item is not None: 171 | self._executorx._change_threads_free_count(-1) 172 | work_item.run() 173 | del work_item 174 | self._executorx._change_threads_free_count(1) 175 | continue 176 | if _shutdown or self._executorx._shutdown: 177 | self._executorx.work_queue.put(None) 178 | break 179 | 180 | 181 | process_name_set = set() 182 | logger_show_current_threads_num = LogManager('show_current_threads_num').get_logger_and_add_handlers( 183 | formatter_template=5, log_filename='show_current_threads_num.log', do_not_use_color_handler=False) 184 | 185 | 186 | def show_current_threads_num(sleep_time=600, process_name='', block=False, daemon=True): 187 | process_name = sys.argv[0] if process_name == '' else process_name 188 | 189 | def _show_current_threads_num(): 190 | while True: 191 | # logger_show_current_threads_num.info(f'{process_name} 进程 的 并发数量是 --> {threading.active_count()}') 192 | # nb_print(f' {process_name} {os.getpid()} 进程 的 线程数量是 --> {threading.active_count()}') 193 | logger_show_current_threads_num.info( 194 | f' {process_name} {os.getpid()} 进程 的 线程数量是 --> {threading.active_count()}') 195 | time.sleep(sleep_time) 196 | 197 | if process_name not in process_name_set: 198 | if block: 199 | _show_current_threads_num() 200 | else: 201 | t = threading.Thread(target=_show_current_threads_num, daemon=daemon) 202 | t.start() 203 | process_name_set.add(process_name) 204 | 205 | 206 | def get_current_threads_num(): 207 | return threading.active_count() 208 | 209 | 210 | if __name__ == '__main__': 211 | from concurrent.futures import ThreadPoolExecutor 212 | 213 | show_current_threads_num(sleep_time=5) 214 | 215 | 216 | def f1(a): 217 | time.sleep(2) # 可修改这个数字测试多线程数量调节功能。 218 | nb_print(f'{a} 。。。。。。。') 219 | return a * 10 220 | # raise Exception('抛个错误测试') # 官方的不会显示函数出错你,你还以为你写的代码没毛病呢。 221 | 222 | 223 | pool = ThreadPoolExecutorShrinkAble(200) 224 | # pool = ThreadPoolExecutor(200) # 测试对比官方自带 225 | 226 | for i in range(30): 227 | # time.sleep(0.5) # 这里的间隔时间模拟,当任务来临不密集,只需要少量线程就能搞定f1了,因为f1的消耗时间短, 228 | # 不需要开那么多线程,CustomThreadPoolExecutor比ThreadPoolExecutor 优势之一。 229 | futurex = pool.submit(f1, i) 230 | # print(futurex.result()) 231 | 232 | # 1/下面测试阻塞主线程退出的情况。注释掉可以测主线程退出的情况。 233 | # 2/此代码可以证明,在一段时间后,连续长时间没任务,官方线程池的线程数目还是保持在最大数量了。而此线程池会自动缩小,实现了java线程池的keppalivetime功能。 234 | time.sleep(1000000) 235 | -------------------------------------------------------------------------------- /threadpool_executor_shrink_able/sharp_threadpoolexecutor0.py: -------------------------------------------------------------------------------- 1 | """ 2 | 史上最强的python线程池。 3 | 4 | 最智能的可自动实时调节线程数量的线程池。此线程池和官方concurrent.futures的线程池 是鸭子类关系,所以可以一键替换类名 或者 import as来替换类名。 5 | 对比官方线程池,有4个创新功能或改进。 6 | 7 | 1、主要是不仅能扩大,还可自动缩小(官方内置的 ThreadpoolExecutor 不具备此功能,此概念是什么意思和目的,可以百度java ThreadpoolExecutor的KeepAliveTime参数的介绍), 8 | 9 | 2、非常节制的开启多线程,例如实例化一个最大100线程数目的pool,每隔2秒submit一个函数任务,而函数每次只需要1秒就能完成,实际上只需要调节增加到1个线程就可以,不需要慢慢增加到100个线程 10 | 官方的线程池不够智能,会一直增加到最大线程数目,此线程池则不会。 11 | 12 | 3、线程池任务的queue队列,修改为有界队列 13 | 14 | 4、此线程池运行函数出错时候,直接显示线程错误,官方的线程池则不会显示错误,例如函数中写1/0,任然不现实错误。 15 | 16 | 这是非完整版 ThreadpoolExecutor ,因为不支持map方法以及future相关的功能,简单地submit可以 17 | """ 18 | import os 19 | import atexit 20 | import queue 21 | import sys 22 | import threading 23 | import time 24 | import weakref 25 | 26 | from nb_log import LoggerMixin, nb_print, LoggerLevelSetterMixin, LogManager 27 | 28 | _shutdown = False 29 | _threads_queues = weakref.WeakKeyDictionary() 30 | 31 | 32 | def _python_exit(): 33 | global _shutdown 34 | _shutdown = True 35 | items = list(_threads_queues.items()) 36 | for t, q in items: 37 | q.put(None) 38 | for t, q in items: 39 | t.join() 40 | 41 | 42 | atexit.register(_python_exit) 43 | 44 | 45 | class _WorkItem(LoggerMixin): 46 | def __init__(self, fn, args, kwargs): 47 | self.fn = fn 48 | self.args = args 49 | self.kwargs = kwargs 50 | 51 | def run(self): 52 | # noinspection PyBroadException 53 | try: 54 | self.fn(*self.args, **self.kwargs) 55 | except BaseException as exc: 56 | self.logger.exception(f'函数 {self.fn.__name__} 中发生错误,错误原因是 {type(exc)} {exc} ') 57 | 58 | def __str__(self): 59 | return f'{(self.fn.__name__, self.args, self.kwargs)}' 60 | 61 | 62 | class ThreadPoolExecutorShrinkAble(LoggerMixin, LoggerLevelSetterMixin): 63 | # 为了和官方自带的THredpoolexecutor保持完全一致的鸭子类,参数设置成死的,不然用户传参了。 64 | MIN_WORKERS = 5 65 | KEEP_ALIVE_TIME = 60 66 | 67 | def __init__(self, max_workers=None, thread_name_prefix=''): 68 | """ 69 | 最好需要兼容官方concurren.futures.ThreadPoolExecutor 和改版的BoundedThreadPoolExecutor,入参名字和个数保持了一致。 70 | :param max_workers: 71 | :param thread_name_prefix: 72 | """ 73 | self._max_workers = max_workers or 4 74 | self._thread_name_prefix = thread_name_prefix 75 | self.work_queue = queue.Queue(max_workers) 76 | # self._threads = set() 77 | self._threads = weakref.WeakSet() 78 | self._lock_compute_threads_free_count = threading.Lock() 79 | self.threads_free_count = 0 80 | self._shutdown = False 81 | self._shutdown_lock = threading.Lock() 82 | 83 | def _change_threads_free_count(self, change_num): 84 | with self._lock_compute_threads_free_count: 85 | self.threads_free_count += change_num 86 | 87 | def submit(self, func, *args, **kwargs): 88 | with self._shutdown_lock: 89 | if self._shutdown: 90 | raise RuntimeError('不能添加新的任务到线程池') 91 | self._adjust_thread_count() 92 | 93 | self.work_queue.put(_WorkItem(func, args, kwargs)) 94 | 95 | def _adjust_thread_count(self): 96 | # if len(self._threads) < self._threads_num: 97 | # self.logger.debug( 98 | # (self.threads_free_count, len(self._threads), len(_threads_queues), get_current_threads_num())) 99 | 100 | if self.threads_free_count < self.MIN_WORKERS and len(self._threads) < self._max_workers: 101 | # t = threading.Thread(target=_work, 102 | # args=(self._work_queue,self)) 103 | 104 | t = _CustomThread(self).set_log_level(self.logger.level) 105 | t.setDaemon(True) 106 | t.start() 107 | self._threads.add(t) 108 | _threads_queues[t] = self.work_queue 109 | 110 | def shutdown(self, wait=True): 111 | with self._shutdown_lock: 112 | self._shutdown = True 113 | self.work_queue.put(None) 114 | if wait: 115 | for t in self._threads: 116 | t.join() 117 | 118 | def __enter__(self): 119 | return self 120 | 121 | def __exit__(self, exc_type, exc_val, exc_tb): 122 | self.shutdown(wait=True) 123 | return False 124 | 125 | 126 | # 两个名字都可以,兼容以前的老名字(中文意思是 自定义线程池),但新名字更能表达意思(可缩小线程池)。 127 | CustomThreadpoolExecutor = ThreadPoolExecutorShrinkAble 128 | 129 | 130 | # noinspection PyProtectedMember 131 | class _CustomThread(threading.Thread, LoggerMixin, LoggerLevelSetterMixin): 132 | _lock_for_judge_threads_free_count = threading.Lock() 133 | 134 | def __init__(self, executorx: ThreadPoolExecutorShrinkAble): 135 | super().__init__() 136 | self._executorx = executorx 137 | 138 | 139 | def _remove_thread(self, stop_resson=''): 140 | # noinspection PyUnresolvedReferences 141 | self.logger.debug(f'停止线程 {self._ident}, 触发条件是 {stop_resson} ') 142 | self._executorx._change_threads_free_count(-1) 143 | self._executorx._threads.remove(self) 144 | _threads_queues.pop(self) 145 | 146 | # noinspection PyProtectedMember 147 | def run(self): 148 | # noinspection PyUnresolvedReferences 149 | self.logger.debug(f'新启动线程 {self._ident} ') 150 | self._executorx._change_threads_free_count(1) 151 | while True: 152 | try: 153 | work_item = self._executorx.work_queue.get(block=True, timeout=self._executorx.KEEP_ALIVE_TIME) 154 | except queue.Empty: 155 | # continue 156 | # self._remove_thread() 157 | with self._lock_for_judge_threads_free_count: 158 | if self._executorx.threads_free_count > self._executorx.MIN_WORKERS: 159 | self._remove_thread( 160 | f'当前线程超过 {self._executorx.KEEP_ALIVE_TIME} 秒没有任务,线程池中不在工作状态中的线程数量是 ' 161 | f'{self._executorx.threads_free_count},超过了指定的数量 {self._executorx.MIN_WORKERS}') 162 | break # 退出while 1,即是结束。这里才是决定线程结束销毁,_remove_thread只是个名字而已,不是由那个来销毁线程。 163 | else: 164 | continue 165 | 166 | # nb_print(work_item) 167 | if work_item is not None: 168 | self._executorx._change_threads_free_count(-1) 169 | work_item.run() 170 | del work_item 171 | self._executorx._change_threads_free_count(1) 172 | continue 173 | if _shutdown or self._executorx._shutdown: 174 | self._executorx.work_queue.put(None) 175 | break 176 | 177 | 178 | process_name_set = set() 179 | logger_show_current_threads_num = LogManager('show_current_threads_num').get_logger_and_add_handlers( 180 | formatter_template=5, log_filename='show_current_threads_num.log', do_not_use_color_handler=False) 181 | 182 | 183 | def show_current_threads_num(sleep_time=600, process_name='', block=False, daemon=True): 184 | process_name = sys.argv[0] if process_name == '' else process_name 185 | 186 | def _show_current_threads_num(): 187 | while True: 188 | # logger_show_current_threads_num.info(f'{process_name} 进程 的 并发数量是 --> {threading.active_count()}') 189 | # nb_print(f' {process_name} {os.getpid()} 进程 的 线程数量是 --> {threading.active_count()}') 190 | logger_show_current_threads_num.info( 191 | f' {process_name} {os.getpid()} 进程 的 线程数量是 --> {threading.active_count()}') 192 | time.sleep(sleep_time) 193 | 194 | if process_name not in process_name_set: 195 | if block: 196 | _show_current_threads_num() 197 | else: 198 | t = threading.Thread(target=_show_current_threads_num, daemon=daemon) 199 | t.start() 200 | process_name_set.add(process_name) 201 | 202 | 203 | def get_current_threads_num(): 204 | return threading.active_count() 205 | 206 | 207 | if __name__ == '__main__': 208 | from concurrent.futures import ThreadPoolExecutor 209 | 210 | show_current_threads_num(sleep_time=5) 211 | 212 | 213 | def f1(a): 214 | time.sleep(0.2) # 可修改这个数字测试多线程数量调节功能。 215 | nb_print(f'{a} 。。。。。。。') 216 | # raise Exception('抛个错误测试') # 官方的不会显示函数出错你,你还以为你写的代码没毛病呢。 217 | 218 | 219 | # pool = ThreadPoolExecutorShrinkAble(200) 220 | pool = ThreadPoolExecutor(200) # 测试对比官方自带 221 | 222 | for i in range(300): 223 | time.sleep(10) # 这里的间隔时间模拟,当任务来临不密集,只需要少量线程就能搞定f1了,因为f1的消耗时间短, 224 | # 不需要开那么多线程,CustomThreadPoolExecutor比ThreadPoolExecutor 优势之一。 225 | pool.submit(f1, str(i)) 226 | 227 | # 1/下面测试阻塞主线程退出的情况。注释掉可以测主线程退出的情况。 228 | # 2/此代码可以证明,在一段时间后,连续长时间没任务,官方线程池的线程数目还是保持在最大数量了。而此线程池会自动缩小,实现了java线程池的keppalivetime功能。 229 | time.sleep(1000000) 230 | --------------------------------------------------------------------------------