├── .gitignore ├── LICENSE ├── README.md ├── runmultithread.py └── runmultithread.spec /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | .venv/ 83 | venv/ 84 | ENV/ 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | 89 | # Rope project settings 90 | .ropeproject 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 JuanPablo AJ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # runmultithread 2 | Run the same process with different inputs in different threads. 3 | 4 | Download from the [releases page](https://github.com/juanpabloaj/runmultithread/releases). 5 | 6 | ![Imgur](http://i.imgur.com/Fu85uhU.png) 7 | 8 | The image was created calling the python interpreter and the code available in [this link](https://gist.github.com/juanpabloaj/dff0e43080b3f6d9c7cac7671613cdc3). 9 | 10 | 11 | ## Development 12 | 13 | The runmultithread.exe file is standalone (just download and use), but if you will make changes in the code, you need have installed in your system this requirements: 14 | 15 | * Qt 4.8.5 16 | * PySide 17 | 18 | ### Create the .exe 19 | 20 | To create the .exe from the code install pyinstaller 21 | 22 | pip install pyinstaller 23 | 24 | And use the .spec file 25 | 26 | pyinstaller.exe runmultithread.spec 27 | -------------------------------------------------------------------------------- /runmultithread.py: -------------------------------------------------------------------------------- 1 | 2 | import multiprocessing 3 | import os 4 | import subprocess 5 | import sys 6 | import time 7 | 8 | from PySide import QtCore, QtGui 9 | 10 | __version__ = "0.2.0" 11 | 12 | 13 | def replace_slash(string): 14 | return string.replace('/', '\\') 15 | 16 | 17 | class WorkerThread(QtCore.QThread): 18 | 19 | def __init__(self, bin, to_run, count, running, completed): 20 | super(WorkerThread, self).__init__() 21 | self.bin = bin 22 | self.to_run = to_run 23 | self.running = running 24 | self.completed = completed 25 | self.count = count 26 | 27 | def run(self): 28 | 29 | proc_args = [self.bin, self.to_run] 30 | proc = subprocess.Popen(proc_args) 31 | 32 | task_name = '#{:03} {}'.format(self.count, self.to_run) 33 | self.running.insertItem(0, task_name) 34 | 35 | while True: 36 | time.sleep(0.5) 37 | retcode = proc.poll() 38 | if retcode is not None: 39 | 40 | item = self.running.findItems( 41 | task_name, QtCore.Qt.MatchExactly 42 | )[0] 43 | self.running.takeItem(self.running.row(item)) 44 | self.completed.insertItem(0, task_name) 45 | 46 | break 47 | 48 | 49 | class Monitor(QtCore.QThread): 50 | 51 | def __init__(self, bin, waiting, running, finished, cpus): 52 | super(Monitor, self).__init__() 53 | 54 | self.create_threads = False 55 | self.waiting = waiting 56 | self.running = running 57 | self.completed = finished 58 | self.cpus = cpus 59 | self.threads = [] 60 | self.bin = bin 61 | 62 | def run(self): 63 | 64 | self.active = True 65 | while self.active: 66 | time.sleep(0.1) 67 | 68 | if self.create_threads: 69 | 70 | free_cpus = self.running.count() < self.cpus 71 | if self.waiting.count() > 0 and free_cpus: 72 | self.new_thread() 73 | 74 | def new_thread(self): 75 | 76 | new_task = self.waiting.takeItem( 77 | self.waiting.count() - 1 78 | ).text() 79 | 80 | threads_count = len(self.threads) + 1 81 | thread = WorkerThread( 82 | self.bin, new_task, 83 | threads_count, self.running, self.completed 84 | ) 85 | thread.start() 86 | self.threads.append(thread) 87 | 88 | 89 | class ControlMainWindow(QtGui.QMainWindow): 90 | def __init__(self, parent=None): 91 | super(ControlMainWindow, self).__init__(parent) 92 | 93 | self.setObjectName("MainWindow") 94 | self.resize(400, 500) 95 | 96 | self.centralwidget = QtGui.QWidget(self) 97 | self.centralwidget.setObjectName("centralwidget") 98 | 99 | self.create_actions() 100 | self.create_menus() 101 | 102 | self.statusBar().showMessage('Experimental Software alpha state') 103 | 104 | self.vLayout = QtGui.QVBoxLayout(self.centralwidget) 105 | self.vLayout.setObjectName("vLayout") 106 | 107 | self.setCentralWidget(self.centralwidget) 108 | 109 | bin_layout = QtGui.QHBoxLayout() 110 | bin_layout.addStretch() 111 | 112 | self.bin_path = '' 113 | self.bin_name_label = QtGui.QLabel('Select a .exe to use') 114 | add_bin_btn = QtGui.QPushButton('Select .exe') 115 | add_bin_btn.clicked.connect(self.select_bin_path) 116 | 117 | bin_layout.addWidget(self.bin_name_label) 118 | bin_layout.addWidget(add_bin_btn) 119 | 120 | add_file_layout = QtGui.QHBoxLayout() 121 | add_file_layout.addStretch(1) 122 | 123 | add_file_btn = QtGui.QPushButton('Add file') 124 | add_dir_btn = QtGui.QPushButton('Add folder') 125 | self.filter_line = QtGui.QLineEdit() 126 | self.filter_line.setPlaceholderText('Extensions to filter files') 127 | 128 | add_file_btn.clicked.connect(self.add_file) 129 | add_dir_btn.clicked.connect(self.add_files_from_folder) 130 | 131 | add_file_layout.addWidget(self.filter_line) 132 | add_file_layout.addWidget(add_file_btn) 133 | add_file_layout.addWidget(add_dir_btn) 134 | 135 | process_vlayout = QtGui.QVBoxLayout() 136 | waiting_label = QtGui.QLabel('Input files') 137 | self.waiting_files = QtGui.QListWidget() 138 | running_label = QtGui.QLabel('Running') 139 | self.running_process = QtGui.QListWidget() 140 | finished_label = QtGui.QLabel('Finished') 141 | self.finished_process = QtGui.QListWidget() 142 | 143 | process_vlayout.addWidget(waiting_label) 144 | process_vlayout.addWidget(self.waiting_files) 145 | process_vlayout.addWidget(running_label) 146 | process_vlayout.addWidget(self.running_process) 147 | process_vlayout.addWidget(finished_label) 148 | process_vlayout.addWidget(self.finished_process) 149 | 150 | run_layout = QtGui.QHBoxLayout() 151 | run_layout.addStretch(1) 152 | 153 | cpu_count_label = QtGui.QLabel('CPU Threads to use') 154 | self.cpu_count = multiprocessing.cpu_count() 155 | self.cpu_spinbox = QtGui.QSpinBox() 156 | self.cpu_spinbox.setMinimum(1) 157 | self.cpu_spinbox.setValue(self.cpu_count) 158 | self.cpu_spinbox.valueChanged.connect(self.update_cpu_count) 159 | self.run_btn = QtGui.QPushButton('Run') 160 | self.run_btn.clicked.connect(self.run_start_stop) 161 | 162 | run_layout.addWidget(cpu_count_label) 163 | run_layout.addWidget(self.cpu_spinbox) 164 | run_layout.addWidget(self.run_btn) 165 | 166 | self.vLayout.addLayout(bin_layout) 167 | self.vLayout.addLayout(add_file_layout) 168 | self.vLayout.addLayout(process_vlayout) 169 | self.vLayout.addLayout(run_layout) 170 | 171 | self.threads = [] 172 | 173 | self.monitor = Monitor( 174 | self.bin_path, 175 | self.waiting_files, self.running_process, self.finished_process, 176 | self.cpu_count 177 | ) 178 | self.threads.append(self.monitor) 179 | self.monitor.start() 180 | 181 | def about(self): 182 | 183 | repo = "https://github.com/juanpabloaj/runmultithread" 184 | 185 | QtGui.QMessageBox.about( 186 | self, "Contact & about", 187 | "runmultithread {version}\n" 188 | "Contact: http://github.com/juanpabloaj/\n" 189 | "Download: {repo}/releases\n" 190 | "\n".format( 191 | version=__version__, repo=repo 192 | ) 193 | ) 194 | 195 | def create_actions(self): 196 | 197 | self.aboutAct = QtGui.QAction( 198 | "&About", self, statusTip="Show the application's About", 199 | triggered=self.about 200 | ) 201 | 202 | self.aboutQtAct = QtGui.QAction( 203 | "About &Qt", self, statusTip="Show the Qt Library's About", 204 | triggered=QtGui.qApp.aboutQt 205 | ) 206 | 207 | def create_menus(self): 208 | self.menubar = self.menuBar() 209 | self.aboutMenu = self.menubar.addMenu('&About') 210 | self.aboutMenu.addAction(self.aboutAct) 211 | self.aboutMenu.addAction(self.aboutQtAct) 212 | 213 | def select_bin_path(self): 214 | if self.running_process.count() > 0 or self.monitor.create_threads: 215 | return 216 | 217 | filename, _ = QtGui.QFileDialog.getOpenFileName(filter='*.exe') 218 | if filename: 219 | filename = replace_slash(filename) 220 | self.bin_path = filename 221 | self.monitor.bin = self.bin_path 222 | self.bin_name_label.setText(filename) 223 | 224 | def add_file(self): 225 | filename, _ = QtGui.QFileDialog.getOpenFileName( 226 | filter=self.filter_line.text() 227 | ) 228 | if filename: 229 | filename = replace_slash(filename) 230 | self.waiting_files.insertItem(0, filename) 231 | 232 | def add_files_from_folder(self): 233 | foldername = QtGui.QFileDialog.getExistingDirectory() 234 | if foldername: 235 | filters = self.filter_line.text().split() 236 | it = QtCore.QDirIterator(foldername, filters) 237 | 238 | while it.hasNext(): 239 | it.next() 240 | 241 | filepath = it.filePath() 242 | 243 | if os.path.isfile(filepath): 244 | 245 | filepath = replace_slash(filepath) 246 | self.waiting_files.insertItem(0, filepath) 247 | 248 | def update_cpu_count(self): 249 | self.cpu_count = self.cpu_spinbox.value() 250 | self.monitor.cpus = self.cpu_count 251 | 252 | def run_start_stop(self): 253 | if not self.monitor.create_threads: 254 | if self.bin_path != '' and self.waiting_files.count() > 0: 255 | self.monitor.create_threads = True 256 | self.run_btn.setText('Pause') 257 | 258 | else: 259 | self.monitor.create_threads = False 260 | self.run_btn.setText('Run') 261 | 262 | def closeEvent(self, event): 263 | self.monitor.active = False 264 | self.monitor.quit() 265 | self.monitor.wait() 266 | 267 | 268 | def main(): 269 | app = QtGui.QApplication(sys.argv) 270 | frame = ControlMainWindow() 271 | frame.show() 272 | sys.exit(app.exec_()) 273 | 274 | 275 | if __name__ == '__main__': 276 | main() -------------------------------------------------------------------------------- /runmultithread.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python -*- 2 | 3 | block_cipher = None 4 | 5 | 6 | a = Analysis(['runmultithread.py'], 7 | pathex=['.'], 8 | binaries=[], 9 | datas=[], 10 | hiddenimports=[], 11 | hookspath=[], 12 | runtime_hooks=[], 13 | excludes=[], 14 | win_no_prefer_redirects=False, 15 | win_private_assemblies=False, 16 | cipher=block_cipher) 17 | pyz = PYZ(a.pure, a.zipped_data, 18 | cipher=block_cipher) 19 | exe = EXE(pyz, 20 | a.scripts, 21 | a.binaries, 22 | a.zipfiles, 23 | a.datas, 24 | name='runmultithread', 25 | debug=False, 26 | strip=False, 27 | upx=True, 28 | console=False ) 29 | --------------------------------------------------------------------------------