Teensy 3.x multithreading library first release

you can set timeslices for each thread, all the way down to 1uS, I use 4 threads with 1ms timeslices same priorities
 
@mjs513

The default timeslice is WAY too large. Setting all thread timeslices to 1 as opposed to the default 100 results in about 1% overhead. Make sure to include thread 0 (which executes loop()): "threads.setTimeSlice(0, 1);".

Using a different timer with 100us ticks (the systick timer used by default has 1000us ticks), results in about 3% overhead with 1 tick timeslices.

Looking at code by mjs513 - I had to update the "threads.setTimeSlice(0, 1);" to "threads.setTimeSlice(0, 10);" as loop() needed more time as it was doing printing.

Question/Issue: I don't see "include thread 0 which executes loop()" noted on the github README. Or in the two library included examples I opened up.

I correctly assumed why it was there and fixed an issue in that code - until I had time to search this thread today - and this is on post #50 of 351 ... so I may find more . . .
 
yeah 0 belongs to normal loop, the extra threads are incremented

Question - It seems loop() is a special thread in that it doesn't need a while(1) to prevent termination?

I also added this to the end of loop(): threads.yield(); :: I assume this acts as anticipated?

Question: Without that .yield() would it call once each 'scheduling cycle' - or would it loop() for the whole allocated timeslice?
 
the loop runs always as is, the yoeld/delay offloads timeslice slots to other threads while your thread is blocking so time is not wasted. the hook on the system timer is what switches between the threads, including the loop. i have never tried yield, but for delay, for example, threads.delay(5000); will block that thread for 5 seconds and will not be switched into until the remainder, so while that happens, only the remaining threads are being switched

regarding the while(1) loop, this i can only assume once the scope exits the thread is destructed, its just there to keep it running per say
 
Thanks - Not clear on loop() calling ... 'once per thread cycle' - or for "whole timeslice" without a .yield()

Oddly when I removed the "threads.setTimeSlice(0, 1);" from setup { trying to see what it did } - something very odd happened - my isr() appeared to stop cycling too? Maybe that was because the system timer was halting and that isr() used micros() as well as the pin change to mark it as completed.

Though .yield in loop() seems safe and good as used - as it is SPECIAL [no while(1)] - but still a scheduled thread in either case.

In current use it must run once per schedule cycle - delay() would break function - though going to suggest on that thread that required code be moved to a 'real thread' so it acts like all the other threads.
 
indeed delay() breaks things, threads.delay(x) was a workaround implemented

i would imagine that running a thread in loops without the scope exiting was the reason why the while(1) was needed, maybe another suggestion on how to make it persist like the main loop wpuld be more beneficial
 
that required code be moved to a 'real thread' so it acts like all the other threads.
Can't resist jumping in here. No problem with moving it into its own thread. The thing that I am sure of is we will have data from two threads being consumed by the third thread. Thread locks? Atomics? Atomics not really that hard to implement since I broke the code on that. Already have it running. Back to the other thread now.
 
re: "indeed delay() breaks things, threads.delay(x) was a workaround implemented"
opps - I meant .delay() or threads.delay() - using that would prevent it from performing in a timely fashion as implemented.

It wasn't running well enough as threads.setTimeSlice(0, 1); - and bumping to (0,10) made it work. So from that I infer that the while(1) for loop() is internal to the scheduler? And without threads.yield() it would consume the whole time slice.

So it seems to be working ( as abused ) - but the README on github ( or examples ) didn't show what to expect or how to use as I read it.

Possibly best to ignore loop() [ loop(){ threads.yield(); } ] and make everything a "while(1){}" thread to get the same behavior from all the code?

Still puzzled why micros() seemed to halt with no system clock updates without the threads.setTimeSlice(0, 1);?
 
Hello!

Can I just ask a noob question?

I'm using the multithreading library for a small project. I'm runnig the accelstepper library in one thread and I'm passing the position data from another thread. I was looking at the library webpage and I cant wrap my head arount the mutex locks and how to use them.

There isn't a noob example of locking the variables (the position variables are declared volatile :) ). On the webpage it says to use Scope if it is possible. Does anyone have a working example on how to lock the variables when changing them

And what is happening on the other thread when the variables are locked? Does the CPU skip the read of the variables and uses the old ones?

Thank you!
 
Hi Tady,

First, in the global scope, you create a unique lock for threads accessing your variable:

Code:
#include "TeensyThreads.h"
Threads::Mutex myLock1;
Threads::Mutex myLock2;

The above demonstrates creating 2 locks, you can create as many as you want, and then to use them we do something like this:

Code:
void loop() {
  if ( someCondition ) {
    [COLOR="#FF0000"]{ Threads::Scope scope(myLock1);[/COLOR]
      myVariable = 0x1234;
    [COLOR="#FF0000"]}[/COLOR]
  }
}

Here we setup a condition as an example, not necessary, the RED highlited area, including brackets, are where the lock is activated. The moment that area is exited, the lock is released automatically. We call that a scope lock, and is the safest mutex lock.

So with the above, we do this example:

Code:
void loop() {
  if ( someCondition ) {
    [COLOR="#FF0000"]{ Threads::Scope scope(myLock1);[/COLOR]
      myVariable = 0x1234;
    [COLOR="#FF0000"]}[/COLOR]
  }
}


void myThread() {
  if ( someCondition ) {
    [COLOR="#FF0000"]{ Threads::Scope scope(myLock1);[/COLOR]
      myVariable = 0x5678;
    [COLOR="#FF0000"]}[/COLOR]
  }
}

in the above code, myLock1 is being used in 2 threads. myVariable is encapsulated in the scope lock. Only the first thread to access that scope will automatically lock the variable. To answer your question on what happens when the other thread runs into the lock, it just sits and wait, typically resembling a *while(1);* (wait forever). The moment the locked thread is done, lock is released, and the waiting thread continues on afterwards. This happens vary fast and not noticable by the user.

All your locking must be of "matching" name.
Different locks can lock in any order, but it's important to know NEVER put a lock WITHIN the same scope of another lock!
Ex:
Code:
void loop() {
  if ( someCondition ) {
    [COLOR="#FF0000"]{ Threads::Scope scope(myLock1);[/COLOR]
      [COLOR="#FF0000"]{ Threads::Scope scope(myLock2);[/COLOR]
        myVariable = 0x1234;
      [COLOR="#FF0000"]}[/COLOR]
    [COLOR="#FF0000"]}[/COLOR]
  }
}


This may lead to a deadlock if not removed in reverse order or if another thread locks myLock2 and then myLock1, you can also deadlock if the same thread activates the same mutex lock like this:
Code:
void loop() {
  if ( someCondition ) {
    [COLOR="#FF0000"]{ Threads::Scope scope([COLOR="#FF0000"]myLock1[/COLOR]);[/COLOR]
      [COLOR="#FF0000"]{ Threads::Scope scope([COLOR="#FF0000"]myLock1[/COLOR]);[/COLOR]
        myVariable = 0x1234;
      [COLOR="#FF0000"]}[/COLOR]
    [COLOR="#FF0000"]}[/COLOR]
  }
}

Your code would appear to be frozen forever. So to keep safe, only lock 1 mutex session per scope, do NOT do recursive locks, and don't lock 2 or more mutexes in the same thread at the same time
 
Last edited:
I couldn't figure out what is thread when this post first released,

@tonton81 nice clear example,

can I also ask some noob question?
is it work as like the two threads(red marks) running simultaneously?
or as time-slicing mentions , switching rapidly from a thread to another?
are the threads run only once when called, and the main script went on and launching all the threads on the way?
in this case locking global variable so the other has to wait, so locking hardware also does that? what about toggling the same GPIO port/pin?

Thank you!
 
the hardware wont lock, the library actually (using timeslices) switches between threads, even if one is “busy” (except if a mutex deadlock occured causing threads to jam. toggling a gpio pin, well, it shouldnt be an issue, but, for ports, serial, uart, spi, i2c, if you have 2 or more threads/libraries accessing the same “port”, they should share the same mutex lock to prevent data corruption on transfers, to simplify things i called mine WIRE1_LOCK and SPI2_LOCK etc, this helps readability in code when using locks. you should definately not use a mutex in an ISR though, that will lock up your entire program if the mutex is locked outside the ISR and the ISR fires and tries to lock it again, itll sit there forever causing a total lockup. I run 4 threads in one of my setups, and between 2 lcds on uarts at 625,000 baudrate, they run as fast as if only one was attached, in a single thread environment, you can see the lag occur when using more than one Lcd... the timeslices also depict how long that timeslice can run before switching, and with threads.delay(1000), if you plan to use delay(), use the threads.delay() instead, it means while waiting for 1 second, give that time to other threads so it doesnt waste it on switching back and forth between timeslice intervals
 
Hello! I have a small question about the "yield" function call. looks like it is not working in my case for Teensy 3.6.
inside the thread function, it works once, but after this it hangs somehow.

Code:
Threads::Mutex logStopLock;
volatile unsigned int fileThreadCounter = 0;
void fileThread(int x);


setup() {
	threads.setMicroTimer();
	threads.setSliceMicros(450);


	loggingPleaseStop = 0;
	threads.addThread(fileThread, 0, 512);
}

void fileThread(int x) {
	
	int stopRequest = 0;
	fileThreadCounter = 0;
	
	sprint("fileThread: starting\n");
	while (1) {
		{
			Threads::Scope m(logStopLock);
			stopRequest = loggingPleaseStop;
		}


		if (stopRequest != 0) {
			
			break;
		}
		fileThreadCounter++;
		digitalWrite(ledPin, HIGH);
		threads.yield();                    //   HERE IT HANGS FOREVER, ON SECOND CYCLE RUN
		digitalWrite(ledPin, LOW);
		//threads.delay(256);
		delay(1000);
	}
	sprint("fileThread: exiting\n");

	Threads::Scope m(logStopLock);
	loggingPleaseStop = 0;
}

loop () {
while (1) {
}
}

Best regards, Igor
 
I noticed that very simple Arduino sketch with yield() is working fine. Maybe the problem is in my custom CMAKe file, something not OK with compiler settings..... I'm using quite a lot of libraries, everything else is working "as expected"... I will try to create a very simple example which will reproduce this issues.

Best regards, Igor
 
OK, the problem was in this: threads.addThread(fileThread, 0, 512);

After I changed it into threads.addThread(fileThread, 0); is starts to work more correctly.
 
Hi,

i skipped through the thread but didn't find an suitable answer to following question:

Are there any (good/ strong) reasons against extending Threads::Scope with a constructor that takes a second argument (int ms), wich in turn is used on the lock() call in the constructor as timeout?

My understanding so far is, either i can use lock(int timeout) to get a lock on the Mutex with yield()'ing until the lock is granted and i have to manually take care of releasing the lock - or i take the sleeker scope lock approach but must pay it with the loss of a lock() call with timeout.

Or am i missing something here?

best regards
 
why would you need a timeout? the moment the scope is done the lock is automatically release, including if you do any returns;.
 
Threading isn't the problem, it's synchronization. I mean in terms of programming complexity.

If one only has a single background task, it might be easier to use a timer callback and a volatile variable (for synchronization).

But this library will be very useful is someone has multiple tasks with different priorities. Speaking of which, I don't see any thread priorities. Could this be a future addition?
 
Last edited:
you can adjust the timeslices to suit priority needs. you can use volatile but your talking about variables. mutexes are to protect the resources (and optionally variables if you wish...) as 2+ threads cannot access the same resource at same time (SPI,UART,CAN,I2C, etc.....)
 
Hi,

why would you need a timeout? the moment the scope is done the lock is automatically release, including if you do any returns;.

I have a multi-tier multi-threads architecture:
  • small lowlevel threads collect data from various sensors with different intervals, wich store the data in intermediate data structures
  • larger application threads that read from the intermediate data (some are chained)
  • a final threads collects the data and transmits it through a RF interface
  • the overall architecture shall align on a common output rate (e.g. the whole runs in cycles)
Given that i need to ensure that the data structures are consistent, i intend to use locks to prevent write-while-reading. Since there are some threads that read from the same source the one-shot lock() can fail, which means i need - in whatever way - to block and try until getting the lock, otherwise a thread will get no new data in a given cycle.

With the current code in the library (lines 512 to 540) the Threads::Scope (mutex&) attempt takes excactly one shot at getting a lock (since there is no timeout for retries) and continues without feedback even if the lock could not be achieved. That way if thread A has a lock, a concurrent thread B cannot get lock but it wouldn't wait either, instead continue without notice.

Thats why i would like to extend Threads::Scope with a timeout option.

Not using the Threads::Scope approach would solve this by manually calling lock(int timeout) and later unlock() - wich would give the blocking behaviour. But the readme suggest using Threads::Scope so i'm willing to stick with it, if it gives the blocking behaviour.

best regards
 
I did a simple test of multi-threading using two sensors, a MPU-9250 IMU on Wire and a BME-280 environment sensor on Wire1.

First, this is the code without multi-threading. For me, using a Teensy 3.6 at 180 MHz, it takes about 900 us to read both sensors.
Code:
#include "EventResponder.h"
#include "MPU9250.h"
#include "BME280.h"

MPU9250 IMU(Wire,0x68);
BME280 BME(Wire1,0x76);

EventResponder myevent;
MillisTimer mytimer;

unsigned long tstart, tstop;

void data(EventResponderRef event)
{
  tstart = micros();
  IMU.readSensor();  
  BME.readSensor();
  tstop = micros();
  Serial.print(IMU.getAccelZ_mss());
  Serial.print("\t");
  Serial.print(BME.getPressure_Pa());
  Serial.print("\t");
  Serial.println(tstop - tstart);
}

void setup()
{
  Serial.begin(115200);
  while (!Serial) ;
  Serial.println("No Thread Test");
  IMU.begin();
  BME.begin();
  myevent.attach(data);
  mytimer.beginRepeating(10, myevent);
}

void loop(){}

Now, this is the code with multi-threading. Using the same setup takes about 780 us to read both sensors.
Code:
#include "EventResponder.h"
#include "TeensyThreads.h"
#include "MPU9250.h"
#include "BME280.h"

volatile bool readImu, readBme;

MPU9250 IMU(Wire,0x68);
BME280 BME(Wire1,0x76);

EventResponder myevent;
MillisTimer mytimer;

unsigned long tstart, tstop;

void get_imu() {
  IMU.readSensor();  
  readImu = true;
}

void get_bme() {
  BME.readSensor();
  readBme = true;  
}

void data(EventResponderRef event)
{
  tstart = micros();
  threads.addThread(get_imu);
  threads.addThread(get_bme);
  
  while((!readImu)||(!readBme)) {
    yield();
  }
  tstop = micros();
  readImu = false;
  readBme = false;
  Serial.print(IMU.getAccelZ_mss());
  Serial.print("\t");
  Serial.print(BME.getPressure_Pa());
  Serial.print("\t");
  Serial.println(tstop - tstart);
}

void setup()
{
  Serial.begin(115200);
  while (!Serial) ;
  Serial.println("Thread Test");
  threads.setMicroTimer(1);
  IMU.begin();
  BME.begin();
  myevent.attach(data);
  mytimer.beginRepeating(10, myevent);
}

void loop(){}

I thought I may have read that you couldn't use two I2C ports simultaneously even though they are different hardware resources. In any case it seems to work well for me, I'm not seeing any data corruption so far.

Brian
 
It's fine for separate ports, just not the same. If your going to use the same port, a mutex lock is required. I must mention though, do NOT use a mutex in an ISR, you WILL deadlock if it's locked elsewhere in loop().

You may also achieve different results with the timeslices value
 
Last edited:
Back
Top