Threading and timeslice

BrianTee

Member
I have a question about thread timing.

Code:
volatile uint counter;

void threadFunction()
{ 
  while(1)
  {
    counter++;
    threads.delay (1000);
  }
}

and...

Code:
void loop()
{

  delay(1000);
                              
}


When this thread is added and its timeslice is 10 msec, how often does the thread run? Is there a complete gap in the main loop for 10 msec when nothing happens?

How often does the cpu "switch over" and run the added thread? And if it does, does it stay there for 10 msec?

And of course the main runs exactly 1,010,000 micro seconds.

So that is the main question, what does the 10 msec signify? The time spent per second? How often is the thread called?

Does it do 1000 instructions on the main, and then 10 on the thread? Or spend 1 msec on the main and then work 0.01 msec on the thread?

Thanks.
 
Last edited:
More info on the Threading tool in use would help. TeensyThreads or other?

With a one second test in loop() the differ in the thread Counter++ could be measured.

But with delay(1000) in both the processor will just be idle waiting for ticks to pass.
 
More info on the Threading tool in use would help. TeensyThreads or other?

With a one second test in loop() the differ in the thread Counter++ could be measured.

But with delay(1000) in both the processor will just be idle waiting for ticks to pass.

Yes the TeensyThreads library. The code itself is just an example, there could be anything in either place. The question focuses on how long does the processor spend in the main before spending time in the the thread and does it stay there for the full 10 msec before returning to main. That sort of thing.

A functioning description of how a single CPU spends its time servicing both the main and the thread and what the timeSlice refers to would be wonderful.
 
The readme might have some answers: github.com/ftrias/TeensyThreads

Code:
Notes on implementation
Threads take turns on the CPU and are switched by the context_switch() function, written in assembly

Only one task can be running at a time - it will run for the established time slice. It can exit early if 'done' - but will be set idle as the next task is made active when the timeslice expires.

A thread with a 10ms timeslice will run 'In Turn' depending on when all other threads are serviced based on the use of their timeslice. If two threads each with 10ms timeslice each would run about 50 times per second, less over head for the context switch, and assuming each thread uses all of it allocated time slice. Executing a threads.delay() should mean it won't run until 1 second has passed, or thread.yield() might be executed when an active thread wants to surrender the remainder of its timeslice because it has no 'work' to do.

Example with 1 second delay on all threads doesn't give any feel for the desired use and where the questions come into play.

Also a complete example is good for others to see and edit.
 
But the readme doesn't say what tells the context_switch() when to run. Or for example if the timeslice is set to 10 msec and it takes 20 msec to run the code, when does it context_switch back to that thread?
 
But the readme doesn't say what tells the context_switch() when to run. Or for example if the timeslice is set to 10 msec and it takes 20 msec to run the code, when does it context_switch back to that thread?

The ReadMe does - the sentence after the indicated text above:
Code:
Notes on implementation
Threads take turns on the CPU and are switched by the context_switch() function, written in assembly. [B][U]This function is called by the SysTick ISR. The library overrides the default systick_isr() to accomplish switching. On the Teensy by default, each tick is 1 millisecond long.[/U][/B] By default, each thread runs for 100 ticks, or 100 milliseconds, but this can be changed by setTimeSlice().

With an allocated 10 ms timeslice the thread "A" will be idled after 10 ms for the next thread.

When other threads finish their timeslices the idled thread "A" will be restored to resume execution where it left off.

If "A" needs 20 ms to complete a task - it will be completed in two 10 ms time slices. When time slice is up - it is just frozen/idled without notice until the start of the next time slice for that thread "A".

If thread "A" is a continuous process, it will execute 10ms at a time between the time given to other threads.
 
The ReadMe does - the sentence after the indicated text above:


If thread "A" is a continuous process, it will execute 10ms at a time between the time given to other threads.

So after the 10 msec is up and the context switches back to loop, how long is it till it is it thread "A" turn again? To keep it simple, there is only thread A and the loop.
 
If both "A" and "loop" have 10ms timeslices - as noted in p#4 - it will return in 10ms.

So every 20ms both threads will run once. And in 1 sec, 1000 ms, both will execute 50 times.
 
But there is only 1 thread and the main loop. Or does the traditional "Loop" as per first post code now considered a thread as well?

If so, how do i set the timeslice of "Loop". I wouldn't want a single function to have as much time at the cpu as the whole main program.

Code:
Re: "So every 20ms both threads will run once. And in 1 sec, 1000 ms, both will execute 50 times."

it doesn't though, it takes 1010 msec. Remove the thread and it takes 1000. Or is that because there is one extra "10 msec" to switch and finish the thread on the last slice?

Edit: Ok, so i did counter++ in the main loop and counter++ in the thread. They both counted to the same value. So they shared the same timeslice value.

How would you set the main traditional "Loop" to a longer timeslice since it isn't a "thread" ?

What would be the ID in : void setTimeSlice(int id, unsigned int ticks) for the main loop to give it longer time?
 
Last edited:
Printing the ID from the normal arduino main loop says it is 0, and the first thread is one.

So if anyone is looking for the answer, the answer is everything is a thread with the main program being thread 0.

Code:
threads.setTimeSlice(0, 100);
threads.setTimeSlice(1, 1);

would set the "Loop" or the main entry to run for 100 msec, and set the first thread "ThreadFunction()" to run for 1 msec.

Setting them all to default of 10 msec for example would run each one for 10 msec. I never realized the threads added AND the main are all threads.

Thank you Fragster for your extreme patience.
 
But there is only 1 thread and the main loop. Or does the traditional "Loop" as per first post code now considered a thread as well?

If so, how do i set the timeslice of "Loop". I wouldn't want a single function to have as much time at the cpu as the whole main program.

Once Threads are enabled - everything is 'a thread' - including loop, as only one thing can run at a time on the single CPU core.

AFAIK by default all threads are given the same value for timeslice. Refer to the docs it seems each thread can be given a unique timeslice value. And as noted a thread can .yield its remaining time to allow return to other Threads when not needed.

Using Threading involves the use of Interrupt execution of registered threads - when time is up, they stop in their tracks to cycle to the next thread. Depending on the code in use this can be problematic or require care for certain devices - some notes to this effect in the ReadMe IIRC.

ooppps - didn't save this 2 hours ago
 
Once Threads are enabled - everything is 'a thread' - including loop, as only one thing can run at a time on the single CPU core.

AFAIK by default all threads are given the same value for timeslice. Refer to the docs it seems each thread can be given a unique timeslice value. And as noted a thread can .yield its remaining time to allow return to other Threads when not needed.

Using Threading involves the use of Interrupt execution of registered threads - when time is up, they stop in their tracks to cycle to the next thread. Depending on the code in use this can be problematic or require care for certain devices - some notes to this effect in the ReadMe IIRC.

ooppps - didn't save this 2 hours ago

This should certainly be included in the docs for threading! I would venture to guess many would assume that only the threads are actually threaded with a whole world of hurt on the main thread if not carefully considering all the effects. The problem i was running into was putting all the i2c operations in a thread - but then during that time RTCM data and 10 hz NMEA data would overflow hardware buffers after a few minutes of operation. It certainly makes sense now of why.

Post above on how to set individual timeslices.
 
This should certainly be included in the docs for threading! I would venture to guess many would assume that only the threads are actually threaded with a whole world of hurt on the main thread if not carefully considering all the effects. The problem i was running into was putting all the i2c operations in a thread - but then during that time RTCM data and 10 hz NMEA data would overflow hardware buffers after a few minutes of operation. It certainly makes sense now of why.

Post above on how to set individual timeslices.

You can post issues on the github to the author is something could be clarified.

As noted, the posting was two hours delayed - walked away with it written on an 'interrupting task' :)

As noted 'Depending on the code in use this can be problematic or require care for certain devices'. Threading can create as many issues as it can solve. Solving those issues takes different effort

Using normal loop processing serialEvent() can pull Serial data from the hardware to local storage for processing - that is called leaving loop each time - or on any delay() that calls yield() or if yield() is explicitly called - or calls could be made manually just to assure the buffers are emptied.

Did some trivial tasks with threading to see it work - but i2c or other hardware getting pulled away without warning can have issues.
 
You can post issues on the github to the author is something could be clarified.

As noted, the posting was two hours delayed - walked away with it written on an 'interrupting task' :)

As noted 'Depending on the code in use this can be problematic or require care for certain devices'. Threading can create as many issues as it can solve. Solving those issues takes different effort

Using normal loop processing serialEvent() can pull Serial data from the hardware to local storage for processing - that is called leaving loop each time - or on any delay() that calls yield() or if yield() is explicitly called - or calls could be made manually just to assure the buffers are emptied.

Did some trivial tasks with threading to see it work - but i2c or other hardware getting pulled away without warning can have issues.

I know this is off topic, but how far away is non blocking i2c? That was the whole reason for going down the thread path. I read the discussion about it, but i get the feeling it has low priority. My experience has been that it is a real bottleneck as I2C is very common. For some reason it never used to be a problem on the Nano at 16 Mhz lol. But 300 usec on a teensy is forever.

I will post an issue with the author.
 
I know this is off topic, but how far away is non blocking i2c? That was the whole reason for going down the thread path. I read the discussion about it, but i get the feeling it has low priority. My experience has been that it is a real bottleneck as I2C is very common. For some reason it never used to be a problem on the Nano at 16 Mhz lol. But 300 usec on a teensy is forever.

I will post an issue with the author.

What Teensy is in use? This library is included in TeensyDuino: github.com/nox771/i2c_t3

But it has not been updated for T_4's. One it's features is an async i2c interface.
 
I know this is off topic, but how far away is non blocking i2c? That was the whole reason for going down the thread path.

Are you using T4.x? If so, the Wire library has calls to yield() within the while loops that "block" (from the caller's perspective). If you use a threading library that overrides yield() to move on to the next thread, then I2C will be non-blocking. I use a very simple cooperative (non-preemptive) multi-threading executive, and I've tested to show that this works. The quote below is a comment on the yield() function from the TeensyThreads source code, so my guess would be that I2C would be non-blocking if you use Wire for I2C on T4.x. I don't know why, but the Wire code for T3.x does not use yield(), but rather has a few places where the code sits in while() loops, with timeouts.

// Yield current thread's remaining time slice to the next thread, causing immediate context switch

EDIT: Serial and SPI also use yield(), so should also be non-blocking if using a thread library that releases the current thread on yield().
 
Are you using T4.x? If so, the Wire library has calls to yield() within the while loops that "block" (from the caller's perspective). If you use a threading library that overrides yield() to move on to the next thread, then I2C will be non-blocking. I use a very simple cooperative (non-preemptive) multi-threading executive, and I've tested to show that this works. The quote below is a comment on the yield() function from the TeensyThreads source code, so my guess would be that I2C would be non-blocking if you use Wire for I2C on T4.x. I don't know why, but the Wire code for T3.x does not use yield(), but rather has a few places where the code sits in while() loops, with timeouts.



EDIT: Serial and SPI also use yield(), so should also be non-blocking if using a thread library that releases the current thread on yield().

Using the teensy 4.1


That is an interesting concept, i will look into it more, thanks!
 
Back
Top