我想稍微自动化手刹并在python中编写一个小程序。 现在我遇到了子进程和线程模块的问题。我想动态地改变我运行的手刹过程的数量。我实现了队列模块,用于获取和放置电影。
CompressThread
调用handbrake类中的encode方法,并对调用_execute
进行编码。现在我想将我在手刹类中读取的进度集中存储在压缩器类中。因此,我可以将进度发布到socketserver
和webgui
。不,我写入sqlite3
数据库,但这应该被删除(因为线程问题),并且仅在退出程序时保存。
我能想到的唯一一种集中保存数据的方法是创建另一个线程,并在CompressThread
类中轮询数据。我的问题是我的程序有4个线程。
有更好的解决方案吗?也许数据库没有错,我不应该删除它?
Compressor类:
class CompressThread(threading.Thread):
""" Manage the queue of movies to be compressed
"""
def __init__(self):
threading.Thread.__init__(self)
self._config = ConfigParser()
self._config.process_config()
self._handbrake = self._config.get_handbrake()
self._lock = threading.Lock()
def run(self):
while True:
movie_id = QUEUE.get()
return_code = self._handbrake.encode(movie_id)
print(return_code)
QUEUE.task_done()
class Compressor(object):
""" Compresses given mkv file
Attributes:
"""
__MAX_THREADS = 1
def __init__(self):
self._dest_audio_tracks = None
self._log = None
self.settings = None
self.config = ConfigParser()
self._database = db.DB()
self._database.connect()
self._running = True
self._threads = []
try:
self.handbrake, self._log = self.config.process_config()
self._log = logging.getLogger("Compressor")
except ConfigError as error:
raise Error("Config error: {0}".format(error))
def process_file(self, input_file, output_file, title):
if not os.path.exists(input_file):
self._log.warning("Input file not exists: {0}".format(input_file))
print("Input file not found: {0}".format(input_file))
else:
media_info = mediainfo.Mediainfo.parse(input_file)
movie_settings = settings.Settings(input_file, title, output_file)
movie_settings.parse(media_info)
self._log.info("Added file {0} to list".format(movie_settings.input_file))
QUEUE.put(self._database.insert_movie(movie_settings))
print("File added.")
def start(self):
self._threads = [CompressThread() for i in range(self.__MAX_THREADS)]
for thread in self._threads:
thread.setDaemon(True)
thread.start()
while self._running:
cmd = input("mCompress> ")
if cmd == "quit":
self._running = False
elif cmd == "status":
print("{0}".format(self._threads))
elif cmd == "newfile":
input_file = input("mCompress> newFile> Input filename> ")
output_file = input("mCompress> newFile> Output filename> ")
title = input("mCompress> newFile> Title> ")
self.process_file(input_file, output_file, title)
def _initialize_logging(self, log_file):
try:
self._log_file = open(log_file, "a+")
except IOError as error:
log_error = "Could not open log file {0}".format(error)
self._log.error(log_error)
raise IOError(log_error)
self._log_file.seek(0)
if __name__ == "__main__":
options_parser = OptionsParser()
args = options_parser.parser.parse_args()
if args.start:
Compressor().start()
手刹类的一部分:
def _execute(self, options):
command = ["{0}".format(self._location)]
if self._validate_options(options):
for option in options:
command.extend(option.generate_command())
print(" ".join(command))
state = 1
returncode = None
process = None
temp_file = tempfile.TemporaryFile()
try:
process = subprocess.Popen(command, stdout=temp_file, stderr=temp_file, shell=False)
temp_file.seek(0)
while True:
returncode = process.poll()
if not returncode:
for line in temp_file.readlines():
p = re.search("Encoding:.*([0-9]{1,2}\.[0-9]{1,2}) % \(([0-9]{1,2}\.[0-9]{1,2}) fps, avg "
"([0-9]{1,2}\.[0-9]{1,2}) fps, ETA ([0-9]{1,2}h[0-9]{1,2}m[0-9]{1,2})",
line.decode("utf-8"))
if p is not None:
self._database.update_progress(p.group(1), p.group(2), p.group(3), p.group(4))
else:
break
temp_file.seek(0)
print(temp_file.readline())
self._write_log(temp_file.readlines())
if returncode == 0:
state = 5
else:
state = 100
raise ExecuteError("HandBrakeCLI stopped with an exit code not null: {0}".format(returncode))
except OSError as error:
state = 105
raise ExecuteError("CLI command failed: {0}".format(error))
except KeyboardInterrupt:
state = 101
finally:
try:
process.kill()
except:
pass
temp_file.close()
return state
else:
raise ExecuteError("No option given")
答案 0 :(得分:2)
完全按照你打算做的去做。
如果这意味着您有5个线程而不是4个,那又是什么?
您的所有线程都不受CPU限制。也就是说,它们不是在处理数字或解析字符串或进行其他计算工作,它们只是等待I / O,外部进程或其他线程。所以创建更多不受CPU限制的线程没有什么害处,除非你已经疯狂到你的操作系统无法顺利处理它们的程度。这是数百个。
如果你的任何线程 受CPU限制,那么即使是2也会太多。在CPython中,*线程必须获得Global Interpreter Lock才能完成任何工作,**因此它们最终不会并行运行,并且花费更多时间来争夺GIL而不是工作。但即便如此,添加另一个非CPU绑定线程,花费所有时间等待CPU绑定线程正在填充的队列不会使事情明显比现有情况更糟。***
至于db ......
SQLite3本身,只要你有足够新的版本,就可以使用多线程。但是,Python sqlite3
模块不是为了向后兼容SQLite3引擎的旧版本。有关详细信息,请参阅文档中的Multithreading。如果我没记错的话(该网站似乎暂时失效,我无法检查),您可以构建第三方模块pysqlite
(stdlib模块所基于的),如果需要,可以使用线程支持。
但是,如果您没有非常大量地使用数据库,那么运行单个线程与数据库通信,并使用队列来侦听其他线程,这是一个非常合理的设计。
*和PyPy,但不一定在其他实现中。
**扩展模块可以释放GIL以在C中工作,只要它们不接触Python中可见的任何值即可。像NumPy这样的知名模块利用了这一点。
***等待线程本身可能受到CPU绑定线程的阻碍,特别是在Python 3.1及更早版本中,但它不会干扰它们。