Report
2 years, 9 months ago.

Keeping a thread object alive without spinning in while loop?

I have an ethernet-based command interpreter running in a thread that gets created by a call to my Start method. It works fine if I create the Thread on the stack and then spin in a while loop afterward. If I remove the while loop, the thread gets destroyed and no longer handles commands, as expected.

However, I was thinking that if I created the thread on the heap, it should continue to process commands, yet it doesn't.

Can anyone explain why this is the case, and also point me to any "best practices" information when using mbed-rtos and its Threads?

Question relating to:

Official mbed Real Time Operating System based on the RTX implementation of the CMSIS-RTOS API open standard. cmsis, rtos, RTX

Although I'm having a bit of trouble (probably too rusty with C++) determining when the Thread is in the Running state via get_state(), I did printf the State of the Thread in various scenarios. Here is what I have seen:

Case #1: Thread created on stack, followed by while(1)

Thread state is Running (1) after creation, then is WaitingMailbox (8) during while(1) loop

Case #2: Thread created on heap, followed by while(1)

Same as Case #1.

Case #3: Thread created on heap, function returns Thread* which I then call get_state() on

Thread state is Running (1) after creation, then is Inactive (0) when the function that created it returns.

posted by Dave M 17 Sep 2014

The next thing that I thought would be interesting to try was the stack / stack size. I decided create a 4k array and then pass this to the function that creates the Thread object. The function then passes this allocated block of memory to the Thread constructor, i.e.

Thread *thread = new Thread(ServerThread, (void *)&package, osPriorityNormal, 4096, stack);

This resulted in a sys_mutex_lock error, so I tried reducing the size of the stack to 1k. Now it doesn't get the sys_mutex_lock error, but the Thread is still terminated when the function returns.

posted by Dave M 17 Sep 2014

Moved comment to answer comments section...

posted by Steven Wilson 19 Sep 2014

This is an excellent post. I learned a lot from this. Thanks Steven Wilson. I performed further experiments on your programs and I found that if you call StartTheThread within main, the thread keeps working (like in program "TestThread 4". However if you call this function from a interruption "InterruptIn" the thread is born dead (state 0). Wonder if threads and interrupts are incompatible??

posted by Cristian Fuentes 31 Mar 2016
Comment on this question

1 Answer

2 years, 9 months ago.

Hi Dave,

The first thing to keep in mind is that a "thread" is not just an object in memory. It is also an "execution context" i.e. it will begin executing a function in parallel (or at least pseudo-parallel) with the other "threads" in the system. The function that it begins to execute is known as the threads entry point (or entry function). If/when that function returns, that thread effectively ends.

Lets take a simple example:

Single Threaded Program

void main(void)
{
    printf("Hello World");
}

In the above example, the program is considered "single threaded" i.e. there is only one thread in existence - the main or "root" thread. Each program starts its root thread with an entry point as the "main" function. Execution begins sequentially one line at a time (jumping to other functions if they are called, and returning when they end). In the above example, the program will eventually exit.

Now typically in an embedded system the main thread will typically not (or at least probably shouldn't) exit:

void main(void)
{
    printf("Hello World");

    while(1)
    {
        //do something useful
    }
}

The key point here is that so long as the threads entry function does not exit/return, the thread will stay in existence. However this does not necessarily mean that the thread has to be actively executing code:

void main(void)
{
    printf("Hello World");

    while(1)
    {
        //do something useful
    
        wait_ms(1000);

    }
}

In the above example, each time round the while loop, the thread will sleep for 1 second (effectively the thread is suspended)

Now, when you create a thread, you do something like the following:

void thread1_func(void const *args)
{
    while(1)
    {
        B1();
        Thread::yield();
        B2();
        Thread::yield();
        B3();
        Thread::yield();
    }
}


void main(void)
{
    Thread thread1(thread1_func);   


    while(1)
    {
        A1();
        Thread::yield();
        A2();
        Thread::yield();
        A3();
        Thread::yield();
    }
}

The above program starts executing at main (as always). The first thing it then does is to create a new thread. The thread "object" is called thread1, and the thread entry function is "thread1_func". This means that the program begins executing thread1_func in parallel with the rest of main.

The thread1 object can be used to query the status of the execution of the threads function (and any child functions) - i.e if it is ready to run, running, waiting, inactive, etc).

Now, it is important to remember that any single-core processor can only execute a single instruction at a time. This effectively means that the processor can only be executing a single instruction within a single thread at a time...but that doesn't necessarily mean that the next instruction the processor executes has to come from the same thread. This is where the operating system and its scheduler come into play.

It is the job of the scheduler to determine (using some algorithm) when the processor should stop executing one thread and start executing another. This act of changing the thread that is executing to a different one is called a context switch.

With multi-threaded programming, a general "rule of thumb" is - don't hog the CPU. Remember, there are multiple threads that all need their turn. In the above example, this is where the yield functions come into play. These basically tell the scheduler to perform a context switch if there is another thread that is ready to run.

Typically a thread will spend most of its life in the "waiting" state e.g. waiting for some data to arrive:

void thread1_func(void const *args)
{
    while(1)
    {
        WaitForSomeDataToArrive();

        ProcessThatData();
    }
}

Once some data arrives, the thread processes it, then goes back to waiting for some more data. The important function here is WaitForSomeDataToArrive(). If no data is available, the thread should "suspend" itself until data is available so that it avoids wasting CPU cycles that could be used by other threads that do have useful processing to do.

One simple way to do this is to go to sleep for a short period of time before waking up again to check if data has arrived:

void WaitForDataToArrive(void)
{
    bool bGotSomeData = false;

    while(bGotSomeData == false)
    {
        bGotSomeData = CheckForData();

        if(bGotSomeData == false)
        {
            Thread::wait(5);
        }
    }
}

The important thing here is that the function doesn't just sit in a "busy" loop checking for data. If no data is available, it actively puts itself to sleep for 5ms. This causes the scheduler to change this thread to the "waiting" state, and perform a context switch to another thread that is "ready to run". After 5ms, the threads state will change to "ready to run" and it will be scheduled to run as soon as possible (note - this does *not* necessarily mean immediately - remember there may be other threads in the middle of executing)

There are other mechanisms that a thread can use to "block" (i.e. wait for something to happen) such as semaphores, mutexs, events, etc. These all provide subtly different mechanisms for controlling concurrent (parallel) execution. The thread would typically wait on one of these objects being "signalled", with these objects being signalled from something like an interrupt, or even another thread. But the principle is the same - the thread blocks as much as possible when it has no data to process so it does not waste CPU cycles spinning idly.

Anyway I hope these late-night (its almost 1am here) ramblings help you understand threads a bit better. I realize it may be a bit of a "brain-dump" on my part, so apologies if it confuses things more.

Regards,

Steven

Hi Steven,

Great answer, I'm sure many users will benefit from your well written detailed explanation.

Charles

posted by Charles Klibbe 18 Sep 2014

+1 Steven

posted by Martin Kojtal 18 Sep 2014

Hi Steven, first of all, thank you for your very detailed answer, which I think many users out there will benefit from. It is very comprehensive, but doesn't directly answer my question.

Part of the problem for me is that I come from the Windows programming world, specifically .NET, and I didn't know what to expect when I started using threads with mbed. For example, if I were to spawn multiple threads and my application exited, those threads would continue to run and my application would remain executing (as shown in the Task Manager).

I think the first two sentences are the ones I'd like to focus on, because they are what's super relevant to my question. What I'm hearing is that when a thread "object" is created (which starts it as well), the responsibility for its execution isn't just handed over to a thread scheduler, and under the hood there are things happening that allow the thread to execute "simultaneously" with other threads. This partially explains why a thread that has been created on the heap gets terminated when the function that it is created in exits. Note that I'm not talking about the entry function here.

For example, if I had something like this:

void thread1_func(void const *args) { while(1) { B1(); Thread::wait(100); } }

void foo(void) { Thread *thread = new Thread(thread1_func); do something here, then exit }

void main(void) { foo(); while(1) { Thread::wait(100); } }

From what I have experimented with, I believe the thread will get terminated. I suppose that I will have to dig into the code to understand what causes that, though I was hoping for a higher level view of what is going on here.

That's why I'd like to find some kind of "best practices" post - something that says (for example), "always create threads in main() and pass a structure to each one that it specifically needs to interact with", or "if you create a thread in a function, never let that function exit". I have already established that the second case is necessary for the thread to continue to function.

posted by Dave M 18 Sep 2014

Hi Dave,

It sounds like you're familiar with general thread behaviour (and all of its "interesting" quirks) :-)

I guess the real question is what happens to the underlying "executing thread" when its respective "thread object" goes out of scope (and/or is deconstructed) - does the executing thread get terminated or does it keep running?

I'll try some experiments myself tonight if I get a chance.

posted by Steven Wilson 18 Sep 2014

Hi Steven, yes, the thread object itself doesn't get destroyed, because I can query its State via get_state() in my main function. In other words, my main() calls a function that creates a thread object, then returns a pointer to the thread object (this was not part of the original design, but merely a way to try to understand what is going on), and the pointer is still valid when the function returns. The State of the thread starts as 1, then 8 before the function exits, and when it exits, the State becomes 0, which is terminated.

I thought that perhaps the stack might have something to do with it, but my experiments proved otherwise.

Thank you so much for bouncing ideas around with me!

posted by Dave M 18 Sep 2014

Hi Dave,

Here are my findings so far....

Thread Test 1

#include "mbed.h"
#include "rtos.h"



DigitalOut led1(LED1);
DigitalOut led2(LED2);
 
void led2_thread(void const *args) 
{
    while (true) 
    {
        
        led2 = !led2;
        Thread::wait(1000);
    }
}
 
int main() 
{
    Thread thread(led2_thread);
    
    while (true) 
    {
        led1 = !led1;
        Thread::wait(500);
    }
}

This is taken from mbed RTOS handbook page (https://mbed.org/handbook/RTOS). In this case, main never exits, the thread object is on the stack and is kept in scope, and the thread function never exits. As expected, the thread continues to run.

Thread Test 2

#include "mbed.h"
#include "rtos.h"



DigitalOut led1(LED1);
DigitalOut led2(LED2);
 
void led2_thread(void const *args) 
{
    while (true) 
    {
        
        led2 = !led2;
        Thread::wait(1000);
    }
}
 
int main() 
{
    Thread* pThread = new Thread(led2_thread);
    
    while (true) 
    {
        led1 = !led1;
        Thread::wait(500);
    }
}

Same as thread test 1, only this time, the thread object is allocated on the heap. Thread runs OK.

Thread Test 3

#include "mbed.h"
#include "rtos.h"



DigitalOut led1(LED1);
DigitalOut led2(LED2);
 
void led2_thread(void const *args) 
{
    while (true) 
    {
        
        led2 = !led2;
        Thread::wait(1000);
    }
}
 
 
void StartTheThread(void)
{
    Thread thread(led2_thread); 
    
    Thread::wait(5000); //give the thread a chance to run
     
}
 
int main() 
{
    StartTheThread();
    
    
    while (true) 
    {
        led1 = !led1;
        Thread::wait(500);
    }
}

This time the thread object is created on the stack within the *StartTheThread* function. This function is called in the context of the main thread which sleeps for 5 seconds after it creates the test thread. The test thread starts and runs for 5 seconds. Once the 5 seconds is up, the StartTheThread function exits, causing the thread object to go out of scope and therefore be deconstructed. This had the effect of stopping the execution of the test thread. This implies that the thread object must remain in existence for the thread to continue running

Thread Test 4

#include "mbed.h"
#include "rtos.h"



DigitalOut led1(LED1);
DigitalOut led2(LED2);
 
void led2_thread(void const *args) 
{
    while (true) 
    {
        
        led2 = !led2;
        Thread::wait(1000);
    }
}
 
 
void StartTheThread(void)
{
    Thread* pThread = new Thread(led2_thread); 
    
    Thread::wait(5000); //give the thread a chance to run
     
}
 
int main() 
{
    StartTheThread();
    
    
    while (true) 
    {
        led1 = !led1;
        Thread::wait(500);
    }
}

Same as test 3, but this time the thread object is allocated on the heap. The thread starts running as before, and continues to run after the 5 second timer expires.

Thread Test 5

#include "mbed.h"
#include "rtos.h"



DigitalOut led1(LED1);
DigitalOut led2(LED2);
 
void led2_thread(void const *args) 
{
    while (true) 
    {
        
        led2 = !led2;
        Thread::wait(1000);
    }
}
 
 

 
int main() 
{
    
    Thread thread(led2_thread);
    
    
    for(int i=0; i<10; i++)
    {
        led1 = !led1;
        Thread::wait(500);
        
        
    }
}

In the above case, the thread object is created on stack, but this time, main no longer has a forever loop. Instead it will exit after 5 seconds (10 iterations, each with a 500ms delay). This time, the thread started as before, but when main exited (and the thread object went out of scope), the thread stopped executing.

Thread Test 6

#include "mbed.h"
#include "rtos.h"



DigitalOut led1(LED1);
DigitalOut led2(LED2);
 
void led2_thread(void const *args) 
{
    while (true) 
    {
        
        led2 = !led2;
        Thread::wait(1000);
    }
}
 
 

 
int main() 
{
    
    Thread* pThread = new Thread(led2_thread);
    
    
    for(int i=0; i<10; i++)
    {
        led1 = !led1;
        Thread::wait(500);
        
        
    }
}

Same as test 5, but this time the thread object is created on the heap. As before the thread starts running, but this time when main exits, the thread continues to run.

Perhaps you could give these examples a go and see if you get the same results?

posted by Steven Wilson 18 Sep 2014

Hi Steven, thanks for posting that. Before I posted my original question, I had performed those same tests. 1, 2, 3 and 5 were almost exactly the same and I had the same results. However, 4 and 6 I did *not* wait for 5 seconds. My original intent was to have a while loop that checks get_state() and waits until the thread was in the running state, but I think I shifted direction at some point and ended up letting the function exit. It could be that a thread remaining in memory really is all that's necessary, as I had originally expected, *except* that I additionally need to wait for the thread to start running! I'll give this a go. Thanks a bunch!

posted by Dave M 18 Sep 2014

Steven, I think we're on the right track - it sure seems like just waiting 5sec after creating the thread makes everything work, so perhaps the thread only terminates when the function exits *if* it isn't yet in a running state. I think the right thing to do now is to figure out when is the right time to leave the function w/o sleeping for an arbitrary period of time.

I alluded to this in the first comment to my original post, which was stating that I was having trouble figuring out what value to test against get_state().

posted by Dave M 18 Sep 2014

Here's the updated code I am currently using - rather than wait a fixed amount of time, I'm waiting for the thread to enter the WaitingMailbox state.

    Thread *thread = new Thread(ServerThread, (void *)&_package);
    while( thread->get_state() != rtos::Thread::WaitingMailbox) {
        Thread::wait( 100);
    }

Unfortunately, I cannot find any information about WaitingMailbox, or why I should be looking for that state before exiting my function... I'm basing that off of observation in the case that the thread is running properly and get_state() returns 8. :) Can you point me in the right direction?

Steven, how did you get the code block in the comment?

posted by Dave M 19 Sep 2014

Are you making some kind of blocking call inside your thread function (e.g. reading from a socket)?

To put code inside code tags use << code>> some code << /code>>...just remove the space after the <<

posted by Steven Wilson 19 Sep 2014

I'm just doing a Thread::wait(20). I would have guessed that would correspond to WaitingDelay or WaitingInterval, but not WaitingMailbox. In uCOS, I vaguely recall using mailboxes for something totally different, like interprocess comms, so it doesn't seem applicable in my scenario.

posted by Dave M 19 Sep 2014

To post an answer, please log in.