Python Multithread: Global data- Key error

I’m doing a little multiplayer game.
“receive-” and “send data” gets its own thread.

I’ve created a test script for simplicity with the same error:

# Testscript: Multithreading

import bge
import time
import _thread as thread

cont = bge.logic.getCurrentController()
own = cont.owner

user = {}



#------------RECEIVE--------------#
# Has its own thread
def receive(own):
    
    while (True):
        
        global user
    
        print("

Receive: Active")
        recData,ip = (111,222)
        user[ip] = recData
        own["ip_other"] = ip
        print ("Receive Data:  "+str(recData))
        print ("IP:  "+str(ip))
        
        time.sleep(7)



#------------SetPosition--------------#
# Try to print the numbers from receive
def setPosition(own):

    global user
    
    print("

SetPosition: Active")
    print ("ip: " +str(own["ip_other"]))
    print ("user[ip]: " +str(user[own["ip_other"]]))



#------------MAIN--------------#
if 'Init' not in own:
    
    own["Init"] = True
    thread.start_new_thread(receive,(own,))

time.sleep(5) # Easier to see when the error occurs ;)
setPosition(own)  

Console:

Blender Game Engine Started

Receive: Active
Receive Data: 111
IP: 222

SetPosition: Active
ip: 222
user[ip]: 111

Receive: Active
Receive Data: 111
IP: 222

SetPosition: Active
ip: 222
Python script error - object 'ServerControl', controller 'TestMultithreading':
Traceback (most recent call last):
    File "Testfile", line 48, in (module)
    File "Testfile", line 38, in setPosition
KeyError: (222)

So in first run it seems to work. In the second cycle it seems to loose the user entry.
This error does not occur without a new thread. (thread.start_new_thread)
I think there is a simple solution. I only can’t see it.

Thanks!

You have at least two issues going on in this code.

First, you are running this is ‘script’ mode. That means that ever time frame Blender is going to clear our your ‘user’ dictionary.

I’m going to call your Blender thread ‘MT’ and your sending thread ‘T1’
MT: <stuff>
will mean things happening on your main thread

Your code runs like this:

MT: user = {}
MT: sleep(5)
T0: put data into user
T0: sleep(7)
MT: read data from ‘user’
MT: reenter the script, user={}
MT: sleep(5)
MT: read key ‘222’ from user, which is {} => key error

If you let your thread run long enough you will find about every 3rd read is successful. Reads are only successful when MT is blocked on ‘sleep(5)’ and T1 wakes up to populate user.

You can fix this by changing this to be a ‘module’ instead of a ‘script’. To do that, you need to take the code that is currently under your "#----- MAIN ----- #’ comment and actually put it under


def main():

To make it an actual function. Then in your Python logic brick you change the mode from ‘script’ to ‘module’ and target “filename.main”. That will prevent ‘user’ from being reset each time the script is executed.

The problem that you have not even had the chance to run into is thread safety. It is possible that T1 can put in ‘user[ip]=recData’ then MT wakes up and reads it before T1 gets the chance to run ‘own[‘ip_other’]=…’

You need to use a semaphore, lock, or other synchronization method to ensure that only one thread at a time is accessing the variable. Something along these lines:


from threading import Lock

user = {}
userLock = Lock()

def threadRun():
  
  with userLock:
    user['ip'] = ...

  sleep(7)

def main():

  # Start thread code

  sleep(5)
  with userLock:
    print(user)


Only one thread at a time can get into a block that starts with ‘with <LockObject>’ so you know that your send thread can get all the data loaded into ‘user’ before the read thread can attempt to read it.

You made my Day!
Also my original script works with the Module solution.
The problem with the lock I had already solved differently, but I’ll also test your variant. Maybe I can thereby improve the performance yet.

Thanks Kastoria!