├── .gitignore ├── LICENSE ├── README.md ├── demo └── server.py ├── setup.py └── torasync ├── __init__.py └── torasync.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | .idea/scopes/scope_settings.xml 59 | .idea/workspace.xml 60 | .idea/.name 61 | .idea/encodings.xml 62 | .idea/misc.xml 63 | .idea/modules.xml 64 | .idea/torasync.iml 65 | .idea/vcs.xml 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # torasync 2 | Run task asynchronously in other processes in easy way 3 | 4 | >很多人在第一次接触tornado的同学都会面临一个问题。tornado的本身提供的异步封装只包括了http请求的封装,那么当我们在使用tornado构建一些复杂逻辑的时候 5 | >会非常的烧脑。异步的语法会传染,哪怕是tornado提供了对yield的支持,但是大多数的py库都没有提供对此的支持。所以这次一不做二不休,干脆就一次性把复杂的 6 | >耗时操作一次性放到自己维护的一堆进程里去执行,再异步的返回执行的结果。 7 | 8 | ## 安装 9 | 10 | >sudo pip install torasync 11 | 12 | 13 | ## 使用 14 | 15 | 详见 demo,内有详细注释,本体代码不超过300行,欲知详情RTFC吧 -------------------------------------------------------------------------------- /demo/server.py: -------------------------------------------------------------------------------- 1 | #coding=utf8 2 | __author__ = 'Alexander.Li' 3 | 4 | ######################################### Notice ##################################### 5 | # 6 | # 本文件演示了torasync的部分用法,更多的玩法期待你来提供 7 | # 8 | ###################################################################################### 9 | 10 | 11 | import sys 12 | sys.path.append("../") 13 | import tornado.ioloop 14 | import tornado.web 15 | from torasync import torasync 16 | 17 | 18 | class MainHandler(tornado.web.RequestHandler): 19 | """ 20 | 基本不会侵入tornado框架本身,可以和tornado的框架的其他特性混用 21 | """ 22 | def get(self): 23 | self.finish("it works!") 24 | 25 | 26 | class SleepNSeccondHandler(tornado.web.RequestHandler): 27 | @tornado.web.asynchronous 28 | def get(self, n_seconds): 29 | torasync.remote_call(self, self.sleepAndResponse, torasync.JsonResponse(), n_seconds) 30 | 31 | @torasync.mapping 32 | def sleepAndResponse(self, context, N): 33 | """ 34 | 这个方法实际上是执行在另外的进程里的,所以在访问全局的成员的时候要小心,全局的成员都被赋值到了子进程中,所以global后都无法同步到 35 | 其他进程的,尽量不要写global的对象,另外 self 对象其实已经不是这个class实例本身了,这点比较魔幻,暂时没想好什么其他办法,所以 36 | 重新定义了一个request类,把webRequest的成员一部分访问form啊body啊file啊的数据结构mock了,替换成了self放这里。 37 | :param context: 就是本文件中那个Context类的实例,在init_processor中返回了这个实例,会在这个地方放进来 38 | :param N:url的参数 39 | :return:返回的数据 40 | """ 41 | import time 42 | time.sleep(float(N)) 43 | return dict(txt="i sleep %s seconds" % N) 44 | 45 | 46 | 47 | application = tornado.web.Application([ 48 | (r"/", MainHandler), 49 | (r"/sleep/([^/]+)", SleepNSeccondHandler), 50 | (r"/static/(.*)", tornado.web.StaticFileHandler, {"path": r"./"}), 51 | ]) 52 | 53 | def timmer(handler, context): 54 | """ 55 | 这个进程后台会一直运行不会返回,sleep固定的时间就可以用来做一些在后台需要定期做的事情 56 | :param handler: 57 | :param context: 58 | :return: 59 | """ 60 | import time 61 | while True: 62 | #这里放一些需要定时执行的工作 63 | time.sleep(1) 64 | 65 | 66 | class Context(object): 67 | db_conn = None 68 | redis_conn = None 69 | 70 | 71 | def init_processor(): 72 | """ 73 | 这个方法在每个工作进程启动的时候都会先执行,用于执行在进程启动的时候的初始化工作,比如数据库连接啊,redis连接啊 74 | :return: 返回进程环境对象 75 | """ 76 | ctx = Context() 77 | ctx.db_conn = None #或者连接数据库 78 | ctx.redis_conn = None #或者redis连接 79 | return ctx 80 | 81 | 82 | if __name__ == "__main__": 83 | application.listen(9527) 84 | ioLoop = tornado.ioloop.IOLoop.instance() 85 | torasync.worker_start(ioLoop, init=init_processor) 86 | torasync.remote_task(None, timmer) #启动一个后台一直跑的进程 87 | ioLoop.start() -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #coding=utf8 2 | __author__ = 'Alexander.Li' 3 | 4 | from setuptools import setup 5 | 6 | setup( 7 | name='torasync', 8 | version='0.0.0.1 pre', 9 | packages=['torasync'], 10 | author='Alexander.Li', 11 | author_email='superpowerlee@gmail.com', 12 | license='LGPL', 13 | install_requires=["tornado>=2.4.1",], 14 | description="Run task asynchronously in other processes in easy way", 15 | keywords ='tornado asynchronous multiprocess', 16 | url="https://github.com/ipconfiger/torasync" 17 | ) -------------------------------------------------------------------------------- /torasync/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf8 2 | __author__ = 'liming' 3 | -------------------------------------------------------------------------------- /torasync/torasync.py: -------------------------------------------------------------------------------- 1 | #coding=utf8 2 | __author__ = 'Alexander.Li' 3 | 4 | import multiprocessing 5 | import logging 6 | import uuid 7 | import signal 8 | import traceback 9 | import sys 10 | 11 | #用来获取worker进程返回数据的队列 12 | CALLBACK_QUEUE = multiprocessing.Queue() 13 | #用来获取主进程发布任务的队列 14 | DISPATCH_QUEUE = multiprocessing.Queue() 15 | #进程池 16 | PROCESSES = [] 17 | #处理器池 18 | PROCESSORS = {} 19 | #注册的后端进程方法 20 | REGISTED_FUNCTIONS = {} 21 | #发起的异步请求 22 | REMOTE_CALLS = {} 23 | #进程环境初始化函数 24 | INIT_FUNC = None 25 | #后端进程执行结果池 26 | TASK_RESULTS = {} 27 | 28 | 29 | class Request(object): 30 | """ 31 | 用于包装Http请求,主要原因是Tornado的request对象没有办法跨进程传输 32 | """ 33 | def __init__(self, httpRequest): 34 | self.arguments = httpRequest.arguments 35 | self.body = httpRequest.body 36 | self.headers = dict([(k, v) for k, v in httpRequest.headers.iteritems()]) 37 | self.cookies = httpRequest.cookies 38 | self.remote_ip = httpRequest.remote_ip 39 | self.files = httpRequest.files 40 | 41 | def get_argument(self, key, default=None): 42 | """ 43 | 获取参数列表 44 | :param key:key 45 | :param default:默认值 46 | :return:返回值 47 | """ 48 | item = self.get_arguments(key, default=default) 49 | if item: 50 | return item[0] 51 | return default 52 | 53 | def get_arguments(self, key, default=None): 54 | """ 55 | 返回数组形式的参数 56 | :param key:key 57 | :param default:默认值 58 | :return: 59 | """ 60 | if key in self.arguments: 61 | return self.arguments[key] 62 | return default 63 | 64 | 65 | class JsonResponse(object): 66 | """ 67 | 直接从后端进程返回Json结果的处理器 68 | """ 69 | def process(self, handler, data): 70 | """ 71 | 处理方法 72 | :param handler: tornado的webRequest对象 73 | :param data: 后端进程返回的json数据 74 | :return: 75 | """ 76 | import json 77 | try: 78 | handler.set_header('Content-Type', 'application/json; charset="utf-8"') 79 | handler.finish(json.dumps(data)) 80 | except: 81 | logging.error(u"error json format:%s", data) 82 | handler.finish(json.dumps({})) 83 | 84 | 85 | class Callback(object): 86 | """ 87 | 执行传入的回调函数,用于自定义后续的处理方式 88 | """ 89 | def __init__(self, callback): 90 | self.callback = callback 91 | 92 | def process(self, handler, data): 93 | """ 94 | 处理方法 95 | :param handler: tornado的webRequest对象 96 | :param data: 后端进程返回的数据 97 | :return: 98 | """ 99 | self.callback(data) 100 | 101 | 102 | class Render(object): 103 | """ 104 | 直接用指定的模板来渲染后端返回的数据 105 | """ 106 | def __init__(self, template): 107 | self.template = template 108 | 109 | def process(self, handler, data): 110 | """ 111 | 处理器方法 112 | :param handler: tornado的webRequest对象 113 | :param data: 后端进程返回的数据 114 | :return: 115 | """ 116 | handler.render(self.template, **data) 117 | handler.finish() 118 | 119 | 120 | def mapping(func): 121 | """ 122 | 用于映射函数到后端进程的装饰器 123 | :param func: 124 | :return: 125 | """ 126 | REGISTED_FUNCTIONS[func.func_name] = func 127 | return func 128 | 129 | 130 | def worker(inQueue, outQueue): 131 | """ 132 | 后端进程函数 133 | :param inQueue:接收任务的队列 134 | :param outQueue:返回数据的队列 135 | :return: 136 | """ 137 | context = INIT_FUNC() if INIT_FUNC else None 138 | while True: 139 | message = inQueue.get() 140 | req_id = None 141 | try: 142 | req_id, method_name, req, args = message 143 | if method_name in REGISTED_FUNCTIONS: 144 | returnValue = REGISTED_FUNCTIONS[method_name](req, context, *args) 145 | outQueue.put((req_id, returnValue)) 146 | except Exception, e: 147 | traceback.print_exc() 148 | if req_id: 149 | outQueue.put((req_id, dict(status=False, error=u"%s" % e))) 150 | 151 | 152 | def sendToBackground(req_id, func_name, message, *args): 153 | """ 154 | 发送任务到后端进程 155 | :param req_id:请求的编号 156 | :param func_name: 函数名称 157 | :param message: 参数数据 158 | :param args: 额外定义的参数 159 | :return: 160 | """ 161 | DISPATCH_QUEUE.put_nowait((req_id, func_name, message, args)) 162 | 163 | 164 | def remote_call(handler, remote_method, processer, *args): 165 | """ 166 | 发起要返回的后端请求 167 | :param handler: tornado的webRequest,一般来说是self 168 | :param remote_method: 要执行的任务定义的函数,这个函数在后端进程执行,小心调用公共区域的成员 169 | :param processer: 处理器的对象 170 | :param args: 额外的参数 171 | :return: 172 | """ 173 | request_id = uuid.uuid4().hex 174 | REMOTE_CALLS[request_id] = handler 175 | PROCESSORS[request_id] = processer 176 | sendToBackground(request_id, remote_method.func_name, Request(handler.request), *args) 177 | 178 | 179 | def remote_task(handler, remote_method, *args): 180 | """ 181 | 发起不需要立即返回的后端请求 182 | :param handler:tornado的webRequest,一般来说是self 183 | :param remote_method:要执行的任务定义的函数,这个函数在后端进程执行,小心调用公共区域的成员 184 | :param args:额外的参数 185 | :return:返回请求的唯一ID 186 | """ 187 | request_id = uuid.uuid4().hex 188 | sendToBackground(request_id, remote_method.func_name, Request(handler.request) if handler else None, *args) 189 | return request_id 190 | 191 | 192 | def try_task(req_id): 193 | """ 194 | 尝试remote_task的返回值 195 | :param req_id: 196 | :return: 返回请求的执行结果 197 | """ 198 | global TASK_RESULTS 199 | if req_id in TASK_RESULTS: 200 | rep = TASK_RESULTS[req_id] 201 | if rep: 202 | del TASK_RESULTS[req_id] 203 | return rep 204 | return None 205 | 206 | 207 | def onSignal(signalnum, stack): 208 | """ 209 | 当主进程收到kill信号量的时候清除所有的子进程 210 | :param signalnum: 211 | :param stack: 212 | :return: 213 | """ 214 | for process in PROCESSES: 215 | try: 216 | process.terminate() 217 | except: 218 | pass 219 | sys.exit(1) 220 | 221 | 222 | def worker_start(ioLoop, init=None, process_count=0): 223 | """ 224 | 开启后端worker进程 225 | :param ioLoop: tornado的ioloop对象 226 | :param init: 所有进程的初始化环境函数,用于比如统一连接数据库之类的事情 227 | :param process_count: 后端进程的数量,不设置的话默认开启cpu数量的2倍 228 | :return: 229 | """ 230 | def callback(*args): 231 | """ 232 | 每次ioloop尝试获取后端返回队列的数据 233 | :param args: 234 | :return: 235 | """ 236 | global TASK_RESULTS, REMOTE_CALLS, PROCESSORS 237 | if not CALLBACK_QUEUE.empty(): 238 | message = CALLBACK_QUEUE.get(False) 239 | try: 240 | global REMOTE_CALLS 241 | req_id, response = message 242 | if req_id in REMOTE_CALLS and req_id in PROCESSORS: 243 | try: 244 | procesor = PROCESSORS[req_id] 245 | procesor.process(REMOTE_CALLS[req_id], response) 246 | except: 247 | traceback.print_exc() 248 | del REMOTE_CALLS[req_id] 249 | del PROCESSORS[req_id] 250 | else: 251 | TASK_RESULTS[req_id] = response 252 | except: 253 | traceback.print_exc() 254 | logging.info("callback message:%s", message) 255 | ioLoop.add_callback(callback) 256 | callback() 257 | signal.signal(signal.SIGTERM, onSignal) 258 | global PROCESSES 259 | global INIT_FUNC 260 | INIT_FUNC = init 261 | if process_count<1: 262 | process_count = multiprocessing.cpu_count() 263 | for i in range(process_count): 264 | process = multiprocessing.Process(target=worker, name="P-%s" % i, args=(DISPATCH_QUEUE, CALLBACK_QUEUE)) 265 | process.start() 266 | PROCESSES.append(process) 267 | logging.info("sub processes started!!") 268 | 269 | --------------------------------------------------------------------------------