raspberrypi / pico-micropython-examples

Examples to accompany the "Raspberry Pi Pico Python SDK" book.
BSD 3-Clause "New" or "Revised" License
1.01k stars 228 forks source link

Thread Example held up by error messages and dies #17

Closed tonygo closed 3 years ago

tonygo commented 3 years ago

When running the example it hangs with a error box. The LED flashes correctly but the 'Done' does not get printed until the error box is cleared.

Thonny gave these error messages:

ManagementError

THONNY FAILED TO EXECUTE COMMAND get_globals

SCRIPT: thonny_helper.print_mgmt_value({name : (__thonny_helper.repr(value), id(value)) for (name, value) in globals().items() if not name.startswith('')})

STDOUT:

STDERR: Traceback (most recent call last): File "", line 1 SyntaxError: invalid syntax

done

I've no idea what is going on

lurch commented 3 years ago

Might be related to https://github.com/micropython/micropython/issues/6899 ? ping @aivarannamaa

aivarannamaa commented 3 years ago

Might be related to micropython/micropython#6899 ?

I think it's related to this problem, indeed.

@tonygo, you may want to make the main thread wait until the background thread finishes, something like:

import _thread

thread_done = False

def work():
    global thread_done
    try:
        ... do work in secondary thread ...
    finally:
        thread_done = True

... do work in main thread ...

_thread.start_new_thread(work)

# Wait for the secondary thread
while not thread_done:
    pass
lurch commented 3 years ago

@aivarannamaa You clearly understand this issue much better than I do. Should we add something similar to https://github.com/raspberrypi/pico-micropython-examples/blob/master/multicore/multicore.py ? Or is Tony's problem more specifically tied to his use of Thonny?

tonygo commented 3 years ago

Thanks for the update. This gets rid of the error messages but sometimes mixes up the prints. One starts printing while the other is already printing. So - only print from one core and printing is VERY slow.

# Trying to understand threads
from sys import exit
import time, _thread, machine

flag = False
def task(n, delay):
    global flag
    led = machine.Pin(25, machine.Pin.OUT)
    for i in range(n):
        led.high()
        time.sleep(delay)
        led.low()
        time.sleep(delay)
    print('done')
    flag = True

_thread.start_new_thread(task, (10, 0.25))

while not flag:
    print("Waiting")
    time.sleep(1)

I do not want waits in the code. The whole point is to do very busy things on separate cores in parallel at the same time. I want to bounce a Raspberry icon in a bounding box as fast as possible in main core and change the colour of 16 Neopixels in a ring every time the berry changes direction in the other. The Neopixel update will be pretty quick and is the 2nd core task.

I can do both separately but will probably need to lock while passing colour value.

aivarannamaa commented 3 years ago

@aivarannamaa You clearly understand this issue much better than I do. Should we add something similar to https://github.com/raspberrypi/pico-micropython-examples/blob/master/multicore/multicore.py ? Or is Tony's problem more specifically tied to his use of Thonny?

The problem may not manifest outside of Thonny, depending on how you are using it. For example, when you save your code as main.py and (soft) reboot, and you leave the REPL alone when the main thread finishes, then you don't see this problem. On the other hand, when the background thread works long enough and you are going to upload a new version of main.py via pyboard.py, you'll probably meet the same problem.

In Thonny it's becomes immediately apparent, because right after the main thread returns to the REPL, Thonny will execute a hidden command for setting up its small helpers for various management tasks (eg, querying global variables or listing files).

aivarannamaa commented 3 years ago

I do not want waits in the code. The whole point is to do very busy things on separate cores in parallel at the same time.

My suggestion does not imply sacrificing parallel processing. The idea is that when the main thread finishes before the background thread, you keep it busy and don't let it leave without its friend. Of course, you need to consider power consumption, so your version with sleep is much better than my simplified example.

tonygo commented 3 years ago

Thank you for ally your help. Initially I was concerned that an example from the Pico Makers caused error messages - not a good sign. I wonder if you know of a simple tutorial which illustrates Locking? I want to be able to use global variables but do not want a clash between cores going for the same variable at the same time.

lurch commented 3 years ago

I wonder if you know of a simple tutorial which illustrates Locking? I want to be able to use global variables but do not want a clash between cores going for the same variable at the same time.

https://micropython.org/ says "multithreading via the "_thread" module, with an optional global-interpreter-lock (still work in progress, only available on selected ports)" so I think for more detailed info you'll have to ask on the MicroPython forum.

tonygo commented 3 years ago

Thanks - I did not know this topic was so close to 'the cutting-edge'.

aivarannamaa commented 3 years ago

@tonygo, here I was asking the advice for joining threads: https://forum.micropython.org/viewtopic.php?f=2&t=9705&p=54275

There seems to be locking implemented for Pico (_thread.allocate_lock), but I don't see an elegant way to use it for joining threads (unless you are assuming that the background thread is able to get the lock before the main thread finishes, which may be the case for all practical purposes, but doesn't feel correct).

jbeale1 commented 3 years ago

The Raspberry Pi "Getting Started" documentation indicates that MicroPython on the Pico is used through Thonny. This gave me to understand that it was the officially suggested method of running MicroPython on the Pico. https://projects.raspberrypi.org/en/projects/getting-started-with-the-pico/2

There is only one provided multicore example on the Raspberrypi Pico Micropython site: https://github.com/raspberrypi/pico-micropython-examples/blob/master/multicore/multicore.py and this generates page-fulls of errors when run via Thonny. It seems to me better to substitute an example which does work from Thonny and without generating errors.

The thread example shown below is simple, easily understood, and also does not generate any errors when run via Thonny, but it does generate an error later when stopped from Thonny. https://www.raspberrypi.org/forums/viewtopic.php?f=146&t=308150&sid=fedebc306fed95f9a029f744f37a2133#p1843844

This longer thread example does not generate any errors at all. It finishes and returns control to the Python REPL. https://www.raspberrypi.org/forums/viewtopic.php?f=146&t=301156&p=1843910#p1843910

aallan commented 3 years ago

The RP2040 MicroPython port has now been merged upstream to the main MicroPython repo, see micropython/micropython#6791, and while we're happy to support the MicroPython team with anything they need, I'd encourage you to open an issue on the MicroPython repo to ask for more example code and documentation to support the RP2040 port.