Я пытаюсь использовать пул работников, предоставляемый concurrent.futures.ProcessPoolExecutor
, чтобы ускорить работу метода внутри класса tkinter. Это связано с тем, что для выполнения метода интенсивность процессора и «распараллеливание» это должно сократить время его завершения. Я надеюсь сравнить его производительность с контролем - последовательное выполнение того же метода. Я написал тестовый код GUI tkinter для выполнения этого теста. Серийное выполнение метода работает, но параллельная часть не работает. Оцените любую помощь, чтобы заставить кодовую часть моего кода работать.Можно ли использовать concurrent.futures для выполнения функции/метода внутри класса tkinter после события? Если да, то как?
Update: Я гарантировал, что я правильно реализовать concurrent.futures.ProcessPoolExecutor
решить мою проблему вне Tk(), то есть из стандартного Python3 сценария. Это объясняется в этом answer. Теперь я хочу реализовать параллельный метод, описанный в этом ответе, для работы с кнопкой в моем tkinter.Tk() графическом интерфейсе.
Мой тестовый код приведен ниже. Когда вы запустите его, появится GUI. Когда вы нажимаете кнопку «FIND», функция _findmatch будет выполняться последовательным и параллельным способом, чтобы узнать, сколько раз число 5 встречается в диапазоне чисел от 0 до 1E8. Серийная часть работает, но параллельная часть жалуется (см. Ниже). Кто-нибудь знает, как исправить эту ошибку травления?
Traceback (most recent call last):
File "/usr/lib/python3.5/multiprocessing/queues.py", line 241, in _feed
obj = ForkingPickler.dumps(obj)
File "/usr/lib/python3.5/multiprocessing/reduction.py", line 50, in dumps
cls(buf, protocol).dump(obj)
_pickle.PicklingError: Can't pickle <class '_tkinter.tkapp'>: attribute lookup tkapp on _tkinter failed
Код проверки:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import tkinter as tk # Python 3 tkinter modules
import tkinter.ttk as ttk
import concurrent.futures as cf
from time import time, sleep
from itertools import repeat, chain
class App(ttk.Frame):
def __init__(self, parent):
# Initialise App Frame
ttk.Frame.__init__(self, parent, style='App.TFrame')
self.parent=parent
self.button = ttk.Button(self, style='start.TButton', text = 'FIND',
command=self._check)
self.label0 = ttk.Label(self, foreground='blue')
self.label1 = ttk.Label(self, foreground='red')
self.label2 = ttk.Label(self, foreground='green')
self._labels()
self.button.grid(row=0, column=1, rowspan=3, sticky='nsew')
self.label0.grid(row=0, column=0, sticky='nsew')
self.label1.grid(row=1, column=0, sticky='nsew')
self.label2.grid(row=2, column=0, sticky='nsew')
def _labels(self):
self.label0.configure(text='Click "FIND" to see how many times the number 5 appears.')
self.label1.configure(text='Serial Method:')
self.label2.configure(text='Concurrent Method:')
def _check(self):
# Initialisation
self._labels()
nmax = int(1E7)
smatch=[]
cmatch=[]
number = '5'
self.label0.configure(
text='Finding the number of times {0} appears in 0 to {1}'.format(
number, nmax))
self.parent.update_idletasks()
# Run serial code
start = time()
smatch = self._findmatch(0, nmax, number)
end = time() - start
self.label1.configure(
text='Serial: Found {0} occurances, Time to Find: {1:.6f}sec'.format(
len(smatch), end))
# Run serial code concurrently with concurrent.futures
workers = 6 # Pool of workers
chunks_vs_workers = 30 # A factor of =>14 can provide optimum performance
num_of_chunks = chunks_vs_workers * workers
start = time()
cmatch = self._concurrent_map(nmax, number, workers, num_of_chunks)
end = time() - start
self.label2.configure(
text='Concurrent: Found {0} occurances, Time to Find: {1:.6f}sec'.format(
len(cmatch), end))
def _findmatch(self, nmin, nmax, number):
'''Function to find the occurence of number in range nmin to nmax and return
the found occurences in a list.'''
start = time()
match=[]
for n in range(nmin, nmax):
if number in str(n): match.append(n)
end = time() - start
#print("\n def _findmatch {0:<10} {1:<10} {2:<3} found {3:8} in {4:.4f}sec".
# format(nmin, nmax, number, len(match),end))
return match
def _concurrent_map(self, nmax, number, workers, num_of_chunks):
'''Function that utilises concurrent.futures.ProcessPoolExecutor.map to
find the occurrences of a given number in a number range in a concurrent
manner.'''
# 1. Local variables
start = time()
chunksize = nmax // num_of_chunks
#2. Parallelization
with cf.ProcessPoolExecutor(max_workers=workers) as executor:
# 2.1. Discretise workload and submit to worker pool
cstart = (chunksize * i for i in range(num_of_chunks))
cstop = (chunksize * i if i != num_of_chunks else nmax
for i in range(1, num_of_chunks + 1))
futures = executor.map(self._findmatch, cstart, cstop, repeat(number))
end = time() - start
print('\n within statement of def _concurrent_map(nmax, number, workers, num_of_chunks):')
print("found in {0:.4f}sec".format(end))
return list(chain.from_iterable(futures))
if __name__ == '__main__':
root = tk.Tk()
root.title('App'), root.geometry('550x60')
app = App(root)
app.grid(row=0, column=0, sticky='nsew')
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
app.columnconfigure(0, weight=1)
app.mainloop()
на «производительность виджета» вы на самом деле означает выполнение блока кода, который выполняется при нажатии на виджет? –
@BryanOakley Мне нравится сравнивать время, проведенное 'self._serial' и' self._concurrent', чтобы найти совпадение. 'self._serial' служит в качестве контроля. Я думаю, что хорошо написанный код сначала попытается реализовать в 'self._check' 2 отдельных потока, чтобы запустить оба этих метода параллельно. Во-вторых, «я»._concurrent' должен содержать cmds для использования 'ProcessPoolExecutor', чтобы найти совпадения и выводить результаты на« label2 »графического интерфейса пользователя. Я пытаюсь достичь этого, но пока не найду способ сделать это. Я читал из другого сообщения, что cmds-параллелизм должен выполняться в основном, поэтому я ссылался на него на 'self.parent'. –
@BryanOakley Ответ на ваш вопрос - да. Это то, что я хочу сделать возможным? Если да, как мне это сделать? Ценю вашу помощь. –