0

Over here I am using multiprocessing to run multiple algorithms in tkinter. At first I tried using threading, but it can't work properly in my program. Below is an idea of my program workflow, it works something like this, but just different functions:

from tkinter import *
from multiprocessing import Process

def SquarFunc(Square):
    for i in range(1,1000):
        Square.set(str(i**2))

def CubeFunc(Cube):
    for i in range(1,1000):
        Cube.set(str(i**3))

if __name__ == "__main__":
    window= Tk()
    Square= StringVar()
    Cube= StringVar()
    window.geometry("500x500")
    A= Label(window, textvariable= Square)
    A.place(x=200, y=200)
    B= Label(window, textvariable= Cube)
    B.place(x=300, y=300)

    Squaring= Process(target=SquarFunc, args=(Square, ))
    Cubing= Process(target=CubeFunc, args=(Cube, ))
    Squaring.start()#Error originates here
    Cubing.start()
    Squaring.join()
    Cubing.join()
    window.mainloop()

The error produced is this:

TypeError: cannot pickle '_tkinter.tkapp' object

Anybody knows how to fix this?? thanks in advance!

Louis
  • 49
  • 1
  • 1
  • 6
  • Does this answer not cover what you need: https://stackoverflow.com/a/26835188/8382028 if not you need to post your code, not just the error – ViaTech Nov 05 '21 at 12:13
  • @ViaTech I've read that link..but I don't really understand what it means, and it's a bit different from my program..let me edit my post and put the code..thanks – Louis Nov 05 '21 at 12:18
  • @ViaTech I've posted my code..do you know how to fix it? – Louis Nov 05 '21 at 12:56
  • provide a [mre] not your entire code, anyways, you can't use `tkinter` in other processes because it is not pickleable – Matiiss Nov 05 '21 at 12:57
  • @Matiiss Ok.... – Louis Nov 05 '21 at 12:58
  • thanks for the example but as I said you can't use `tkinter` stuff in other processes. `StringVar` comes from `tkinter` so you can't put it in another process, you will need to use `multiprocessing.Queue` or `multiprocessing.Manager` that will get data from the process and then use an `.after` loop to update the `StringVar`s, I will show you an example, also don't use `.join`, it will block the `mainloop` – Matiiss Nov 05 '21 at 13:28
  • @Matiiss what do you mean by pickleable? And can you suggest other ways in doing this..by the way, I have provided a minimal reproducible example of my program..thanks – Louis Nov 05 '21 at 13:28
  • In multiprocessing, each process has its own copy of all the data. That means that on intialization, the data must be serialized to be passed to the new process, and `pickle` is used for that. But it is only your first problem: each worker process will only change its own copy and the main process will not be aware of the changes, you should either have each worker process return serializable data, or use a *managed* data container, which is expensive because or synchronization problems. Long story made short multi processing is a very efficient tool in some use cases but is not a magic wand. – Serge Ballesta Nov 05 '21 at 13:30

1 Answers1

2

Here is an example of how to communicate with other processes if using multiprocessing (explanation is in comments, time.sleep is used just for the example because otherwise those loops will complete in a few microseconds):

from tkinter import Tk, StringVar, Label
from multiprocessing import Process, Manager
import time


def square_func(d, name):
    for i in range(1, 1000):
        # update data in the shared dict
        d[name] = i
        time.sleep(0.1)


def cube_func(d, name):
    for i in range(1, 1000):
        # update data in the shared dict
        d[name] = i
        time.sleep(0.1)


def update_string_vars(d, *variables):
    for var in variables:
        # get the value from shared dict
        value = d[str(var)]
        if value is not None:
            # set string var to the value
            var.set(str(value))
    # schedule this to run again
    window.after(100, update_string_vars, d, *variables)


# cleanup process upon closing the window in case 
# processes haven't finished
def terminate_processes(*processes):
    for p in processes:
        p.terminate()


if __name__ == "__main__":
    window = Tk()
    window.geometry("500x500")
    # bind the terminator to closing the window
    window.bind('<Destroy>', lambda _: terminate_processes(
            square_process, cube_process))

    square_var = StringVar()
    cube_var = StringVar()

    Label(window, text='Square:').pack()
    Label(window, textvariable=square_var).pack()

    Label(window, text='Cube:').pack()
    Label(window, textvariable=cube_var).pack()
    
    # create the manager to have a shared memory space
    manager = Manager()
    # shared dict with preset values as to not raise a KeyError
    process_dict = manager.dict({str(square_var): None, str(cube_var): None})

    square_process = Process(
        target=square_func, args=(process_dict, str(square_var))
    )
    cube_process = Process(
        target=cube_func, args=(process_dict, str(cube_var))
    )
    square_process.start()
    cube_process.start()
    
    # start the updater
    update_string_vars(process_dict, square_var, cube_var)

    window.mainloop()

Useful:

See also:
I strongly advise against using wildcard (*) when importing something, You should either import what You need, e.g. from module import Class1, func_1, var_2 and so on or import the whole module: import module then You can also use an alias: import module as md or sth like that, the point is that don't import everything unless You actually know what You are doing; name clashes are the issue.

I strongly suggest following PEP 8 - Style Guide for Python Code. Function and variable names should be in snake_case, class names in CapitalCase. Don't have space around = if it is used as a part of keyword argument (func(arg='value')) but have space around = if it is used for assigning a value (variable = 'some value'). Have space around operators (+-/ etc.: value = x + y(except here value += x + y)). Have two blank lines around function and class declarations. Object method definitions have one blank line around them.

Matiiss
  • 5,970
  • 2
  • 12
  • 29