I do not understand how to combine multiprocessing with Kivy, a Python GUI library, so please help me on this.
Initially, I had created a code using Kivy, a Python GUI library, and threading.Thread, as follows.
#-*- coding: utf-8 -*-
from kivy.lang import Builder
Builder.load_string("""
<TextWidget>:
BoxLayout:
orientation: 'vertical'
size: root.size
Button:
id: button1
text: "start"
font_size: 48
on_press: root.buttonClicked()
""")
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import StringProperty
import threading
import time
class TextWidget(Widget):
def __init__(self, **kwargs):
super(TextWidget, self).__init__(**kwargs)
self.process_test = None
def p_test(self):
i = 0
while True:
print(i)
i = i + 1
if self.ids.button1.text == "start":
break
def buttonClicked(self):
if self.ids.button1.text == "start":
self.process_test = threading.Thread(target=self.p_test)
self.process_test.start()
self.ids.button1.text = "stop"
else:
self.ids.button1.text = "start"
self.process_test.join()
class TestApp(App):
def __init__(self, **kwargs):
super(TestApp, self).__init__(**kwargs)
self.title = 'testApp'
def build(self):
return TextWidget()
if __name__ == '__main__':
TestApp().run()
This code simply displays a single button, and when the button is pressed, it executes a print statement in a while loop.
This code, being a simple example, worked without any issues.
However, as the Kivy GUI definition file became larger, and the CPU processing load of the program running inside the p_test function increased, the program started to become choppy.
According to my machine's task manager, despite having plenty of CPU capacity, it seemed there were limitations to processing everything within a single process.
To circumvent this issue, I decided to use multiprocessing. However, I find the use of multiprocessing complex and hard to understand, so I would like to learn more about how to use it.
First, I replaced the threading.Thread in my earlier code with multiprocessing.Process as follows.
#-*- coding: utf-8 -*-
from kivy.lang import Builder
Builder.load_string("""
<TextWidget>:
BoxLayout:
orientation: 'vertical'
size: root.size
Button:
id: button1
text: "start"
font_size: 48
on_press: root.buttonClicked()
""")
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import StringProperty
import time
from multiprocessing import Process
class TextWidget(Widget):
def __init__(self, **kwargs):
super(TextWidget, self).__init__(**kwargs)
self.process_test = None
def p_test(self):
i = 0
while True:
print(i)
i = i + 1
if self.ids.button1.text == "start":
break
def buttonClicked(self):
if self.ids.button1.text == "start":
self.process_test = Process(target=self.p_test, args=())
self.process_test.start()
self.ids.button1.text = "stop"
else:
self.ids.button1.text = "start"
self.process_test.join()
class TestApp(App):
def __init__(self, **kwargs):
super(TestApp, self).__init__(**kwargs)
self.title = 'testApp'
def build(self):
return TextWidget()
if __name__ == '__main__':
TestApp().run()
Unfortunately, this code did not work correctly and resulted in an error. The error message was as follows.
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "C:\Users\taichi\Documents\Winpython64-3.11.5.0\WPy64-31150\python-3.11.5.amd64\Lib\multiprocessing\spawn.py", line 122, in spawn_main
exitcode = _main(fd, parent_sentinel)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\taichi\Documents\Winpython64-3.11.5.0\WPy64-31150\python-3.11.5.amd64\Lib\multiprocessing\spawn.py", line 132, in _main
self = reduction.pickle.load(from_parent)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
EOFError: Ran out of input
I learned that I must use multiprocessing directly under if **name** == '**main** ':
. However, this makes it difficult to pass values between Kivy and the multiprocessing code, and I'm not sure how to handle it.
The code I created as a trial is as follows.
#-*- coding: utf-8 -*-
from kivy.lang import Builder
Builder.load_string("""
<TextWidget>:
BoxLayout:
orientation: 'vertical'
size: root.size
Button:
id: button1
text: "start"
font_size: 48
on_press: root.buttonClicked()
""")
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import StringProperty
import threading
import time
from multiprocessing import Process
class TextWidget(Widget):
def __init__(self, **kwargs):
super(TextWidget, self).__init__(**kwargs)
self.process_test = None
def buttonClicked(self):
if self.ids.button1.text == "start":
self.ids.button1.text = "stop"
else:
self.ids.button1.text = "start"
class TestApp(App):
def __init__(self, **kwargs):
super(TestApp, self).__init__(**kwargs)
self.title = 'testApp'
def build(self):
return TextWidget()
def p_test(count, array):
i = 0
while True:
print(i)
i = i + 1
if __name__ == '__main__':
#shared memory
count = Value('i', 0)
array = Array('i', 0)
process_kivy = Process(target=TestApp().run(), args=[count, array])
process_kivy.start()
process_test = Process(target=p_test(), args=[count, array])
process_test.start()
process_kivy.join()
process_test.join()
I created the above code because I learned that using shared memory allows for data sharing between multiprocessing instances. However, I don't understand how to pass data to a class with shared memory.
I want to set it up so that the while loop starts only when a Kivy's button is pressed, but in reality, the print statement is executed after the Kivy GUI is closed in this code.
Furthermore, since the Kivy program also needs to be launched with multiprocessing, I don't know how to join my own process to itself.
How can I use multiprocessing correctly?
I am using Windows11 and WinPython.
Using the hints in the github response and your latest code, here is a version of your code that does what I think you want:
from kivy.app import App
from kivy.uix.widget import Widget
from multiprocessing import Process, Value
from kivy.lang import Builder
Builder.load_string("""
<TextWidget>:
BoxLayout:
orientation: 'vertical'
size: root.size
Button:
id: button1
text: "start"
font_size: 48
on_press: root.buttonClicked()
Label:
id: lab
text: 'result'
""")
class TextWidget(Widget):
def __init__(self, **kwargs):
super(TextWidget, self).__init__(**kwargs)
self.process_test = None
self.proc = None
# shared memory
self.count = Value('i', 0)
def buttonClicked(self):
if self.ids.button1.text == "start":
self.proc = start_process(self.count)
self.ids.button1.text = "stop"
else:
if self.proc:
self.proc.kill()
self.ids.button1.text = "start"
self.ids.lab.text = str(self.count.value)
class TestApp(App):
def __init__(self, **kwargs):
super(TestApp, self).__init__(**kwargs)
self.title = 'testApp'
def build(self):
return TextWidget()
def start_process(count):
process_test = Process(target=p_test, args=[count], daemon=True)
process_test.start()
return process_test
def p_test(count):
i = 0
while True:
print(i)
i = i + 1
with count.get_lock():
count.value = i
if __name__ == '__main__':
TestApp().run()
The key point is that the Process
creation does not include the self
of a kivy widget. I suspect that your code would work if the self
was a reference to a simple Python object.