Using multiprocessing issue with Kivy

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?

Hello,
here is a link which should explain more about using MP with Kivy and data transfer via mp.Queue
and how to avoid the second blank kiv-window (sorry no shared memory)

kivy multiprocessing example

1 Like

Thank you very much.
It worked as I expected.

Hello taichi,

The transfer of numerical values went smoothly, but I have noticed that passing strings in Kivy does not work well. Does anyone know a solution to this problem?

depends on the type of strings and the method of passing them

needs more explanation, code example will be fine

Using shared memory of the array type results in an error.

follow the docs: multiprocessing.Array

e.g. modified code & best regards Frank

def buttonClicked(self):
   if self.ids.button1.text == "start":
      self.proc = start_process(self.array,self.lock)
      self.ids.button1.text = "stop"
   else:
      if self.proc:
         self.proc.kill()
      self.ids.button1.text = "start"
      
      # --
      print(self.array)
      # < SynchronizedString wrapper
      # for < multiprocessing.sharedctypes.c_char_Array_35 object at 0x7f7448142340 >>

      # --
      print(type(self.array))
      # class 'multiprocessing.sharedctypes.SynchronizedString'>

      # -- orig line
      #self.ids.lab.text = str(self.array.decode("utf-8"))
      
      # -- use the Array class method or property
      self.ids.lab.text = self.array.value.decode("utf-8")

Hello taichi,

The transfer of numerical values went smoothly, but I have noticed that passing strings in Kivy does not work well. Does anyone know a solution to this problem?

needs more explanation, code example will be fine

from your code and the example below it is not related to kivy.

It is related to the type of mp.Array

Using shared memory of the array type results in an error.

follow the docs : multiprocessing.Array

e.g. fixed code & best regards Frank

def buttonClicked(self):
   if self.ids.button1.text == "start":
      self.proc = start_process(self.array,self.lock)
      self.ids.button1.text = "stop"
   else:
      if self.proc:
         self.proc.kill()
      self.ids.button1.text = "start"
      # --
      print(self.array)
      # < SynchronizedString wrapper
      # for < multiprocessing.sharedctypes.c_char_Array_35 object at 0x7f7448142340 >>

              # --
      print(type(self.array))
           # class 'multiprocessing.sharedctypes.SynchronizedString'>

      # -- orig code
      #self.ids.lab.text = str(self.array.decode("utf-8"))
      # -- fixed
      self.ids.lab.text = self.array.value.decode("utf-8")