Adding a short time.sleep() to pygame main loop makes a different thread run much faster

Viewed 99

This behavior seems really odd to me. I'm running a main pygame loop in which I process the fastevent queue, and I have a separate thread running that actually runs the game. The odd thing is that if I add a short sleep statement within my main loop, the game thread executes much faster. Here's the code for the main loop:

exited = False
while not exited:
    if launcher.is_game_running:
        launcher.game.grm.board.update_turn_timer()

    # Run the event loop so pygame doesnt crash. Note: This loop CANNOT be
    # allowed to hang. It must execute quickly. If any on_click function
    # is going to stall and wait for user input, it had better process the
    # fastevent queue iteslf.

    # TODO(?) I don't know why, but having this sleep in here *speeds up*
    #         the game execution by a SIGNIFICANT factor. Like 10x. As far
    #         as I can tell, the value in the sleep can be anything small.
    time.sleep(0.001)

    for event in pygame.fastevent.get():
        if event.type == pygame.QUIT:
            exited = True
            break

        # Handle clicks, mouse movement, keyboard, etc
        launcher.handle_event(event)

    if len(launcher.delayed_on_click_effects) > 0:
        launcher.delayed_on_click_effects.popleft()()

I'm really at a loss here, I don't see how adding that sleep could possibly speed up the execution of the other thread. Any ideas? I know this code snippet isn't enough to know what's going on in the other thread and such. I would post more code, but I have so little idea about what's going on here that I don't know which parts of my codebase are actually relevant. Can post more if anyone has suggestions.

I wasn't planning on worrying about this too much, but now a new change I've introduced is slowing my runtime back down again. Without knowing what's actually going on, it's hard to figure out how to get the runtime back where it was.

1 Answers

Thanks Thomas - I had no idea the GIL was even a thing, but yes, it looks like my issue is that certain threads are CPU-intensive and are not releasing the GIL frequently enough for the other threads.

I had noticed that I could replace the time.sleep(0.001) in my main loop with a print statement, and I would get the same speedup effect on the other thread. This makes sense if what that sleep is doing is releasing the GIL, because prints also release the GIL.

The "new change I've introduced" that I mentioned in the post was adding more threads (which handled message passing between the game client and a server). So my suspicion is that one of these new threads is CPU-intensive and is not releasing the GIL, thus partially starving the game thread.

To try to debug this, I added a bunch of print statements wherever I was creating new threads just to make sure I understood how many I had. And it turns out, these print statements fixed the runtime issues. So apparently, one of the places where I just added a print statement was within a thread that was hogging the GIL. The new print statement releases the GIL, allowing the game thread to run.

So my takeaways from this are:

  • The GIL exists (good to know)
  • Only one thread can actually execute at a time
  • If you want a thread to "wait and let other threads do things", then you should release the GIL with an i/o call (print, socket.recv, etc) or with time.sleep()
  • Threads should not "wait" by, eg, executing a while loop and checking for some condition to be true. This will hog the GIL and slow down other threads (unless you make sure to release the GIL each iteration of the loop with a sleep)
Related