Particle threads tutorial

Thanks but what headers do I need to include in my application to access the underlying method? Just include "FreeRTOS.h doesn’t work.

How do I ensure that the Threads begin only after the setup() has completed?

Can the below statement be written inside the setup() instead of at the top of the file?

Also, the below logic doesn't seem to work for me.

void threadFunction(void)
{
     /* Wait for the setup() to complete */
    while( !gSetupCompleted )
    {
        continue;
    }
    while(true)
    {
        // actual thread doing the task
    }
}

where gSetupCompleted is set to true at the end inside setup().

Thanks | Regards,
Dipen

There already is a post in this thread that shows how you can

But also in the opening post Rick has shown how to use a mutex to hold your thread off from running.

1 Like

Namaste @ScruffR,

Thank you for your prompt reply. I should have gone through the entire post before posting my query :wink:

The post from @justicefreed_amper was just what I was looking for. So thanks to @justicefreed_amper too!

I’ve implemented the logic to start the Threads just before exiting the setup() and, my firmware is working as desired.

Thanks | Regards,
Dipen

2 Likes

Just make sure to keep in mind that my first post in that thread only works in firmware versions v0.6.4 and below - I’m now personally in favor of using a boolean flag to start and pause the thread:

bool run_thread = false;
...
void thread_function(void) {
     while (run_thread == false) delay(100);
     // do init / setup for your thread here
     while (true) {
          while(run_thread == false) delay(100);
          // do main thread stuff here
          delay(40);
     }
}

setup() {
     // setup stuff
     ...
     SINGLE_THREADED_BLOCK {
          run_thread = true;
     }
}

@justicefreed_amper, @ScruffR, if the above holds valid, I'm surprised that my firmware is not creating problems with Device OS ver 1.1.0. My implementation is exactly as suggested in the post #4 of this thread. It's over 24 hours now since the firmware is running and readings are successfully getting published on the Cloud. No issues have been observed, at least till now.

Thanks | Regards,
Dipen

This comment in post #5 may expain that

However, I'd rather use Thread *myThread; and myThread = new Thread("myThreadName", myThreadFunction); to avoid creation of a dummy object.

2 Likes

@ScruffR @rickkas7

When using mutexes, I ended up having to put

myThread = new Thread("myThreadName", myThreadFunction);

in the setup() function so that the mutex init (i.e. os_mutex_create(&mutex);) could be guaranteed to happen before the thread executed.

If I placed the os_mutex_create() call in the STARTUP() block per the example, it failed with an SOS:1 message when the os_mutex_lock() was called in the thread.

On system firmware 1.0.1.

1 Like

Same issue in DeviceSO 1.4.1-rc.1

1 Like

@rickkas7 Any ideas how to access the FreeRTOS function from user code?

You can’t access the native FreeRTOS functions from modular builds, which are the normal type of build on all devices except the Spark Core.

In modular builds, the system part, where FreeRTOS is linked in, is separate from the user firmware and only functions that have been explicitly exported from the hardware abstraction layer (HAL) can be called.

If you build a monolithic build (Device OS and user firmware in a single binary) you can call FreeRTOS directly. The Particle: Compile for debug (local) in Workbench creates monolithic builds.

1 Like

@rickkas7 Is there a differnece of setting up a thread this way:

thread = new Thread("PublishQueueAsync", threadFunctionStatic, this, OS_THREAD_PRIORITY_DEFAULT, 3072); 

vs (from the above tutorial)

Thread thread("testThread", threadFunction);

Yes. The original example is a globally constructed Thread object, which does not work on recent versions of Device OS because C++ does not define the order in which globally constructed object are constructed, the Thread object can be constructed before things it depends on have been constructed. Doing it from setup() with new is safer (and works).

2 Likes

@rickkas7 with the new Tread how small can it be to reserve space for it (ie the 3072)?
thread = new Thread("PublishQueueAsync", threadFunctionStatic, this, OS_THREAD_PRIORITY_DEFAULT, 3072);

The minimum stack size depends on what you do from your thread. If you only check GPIO and set flags, it could be as small as 256 bytes. If you allocate data structures on the stack or use Particle.publish, it might need to be 1K to 2K.

1 Like

Thanks! Just to follow up on this, how would I know if its set too low? Does the thread become unstable and crash for example?

For the below: what is this used for? is it used to pass through a parameter?
thread = new Thread(“PublishQueueAsync”, threadFunctionStatic, this, OS_THREAD_PRIORITY_DEFAULT, 3072);

this is a C++ keyword that provides a self-reference-pointer to an object itself.
So in this context the respective code line is meant to be called from withing an object to allow the thread function to access the object itself.

But this parameter could be a pointer to a parameter of any kind (as seen when looking at the implementation)

1 Like

@rickkas7, this is an interesting comment. Is it possible to make a guarantee that all particle API processes called within Particle's system thread are thread safe? If not, then wouldn't that mean, baring an exhaustive list of all API calls used by deviceOS, that it's not safe to use any non-thread safe Particle API calls, even if there're only used in the main user loop() thread?

Do you also know if it can be guaranteed that all deviceOS threads will yield, the conclusion being that if all user threads yield as well that the remainder of the time is used up by the RTOS's minimal busy wait thread?

BTW, has Particle ever considered turning off the configUSE_PREEMPTION flag? Having pre-emption in a tiny microcontroller with limited RAM is perhaps why "Limited memory and no virtual memory makes using threads impractical" can be frequently true. Users would have to be more careful about yielding threads, but this isn't so different from making sure loop() functions don't suck up too much time.

At least for us, the improved architecture of dedicated threads is orders of magnitude superior to an FSM. It's very easy to review independent threads and make sure they do not interact with the system except via mailboxes and queues. On complex state machines where certain subsections are 100% independent, IMHO it's much harder to execute reviews. It takes a certain formal discipline to exhaustively look for all thread "leakage", but the long-term results are well worth the upfront cost.

@kubark42, even the use of threads, queues and messages can cause problems. I wish more embedded designed used Active Objects, which combine threads with hierarchical FSMs. Best of both worlds IMO.

The problem is that in the absence of a list of guaranteed thread-safe functions, you have to assume that they’re not. A function could happen to work now, but without having a list of functions that are guaranteed to be thread safe in the future, you never know. It may be an acceptable risk that it could stop working in the future in some cases.

Not all resources are correctly guarded. SPI wasn’t until 1.5.0-rc.1, and I’m not positive I2C is. SPI, I2C, and Serial require manually protecting against simultaneous use from user firmware.

In the future these things will be fixed, but right now it’s mostly undocumented and use at your own risk.

Preemption is enabled, so each thread will normally only take at most a 1 millisecond time slice if it does not yield or call something that yields, like delay(). However, disabling interrupts or using a SINGLE_THREADED_BLOCK can allow a thread to continue to run forever without swapping.

1 Like