table des matières
  1. Les threads
    1. Instanciation d’un thread
    2. Communication et synchronisation de thread
  2. EventQueue
  3. Synchronisation des données
    1. Mutex
    2. Modèle producteur/consommateur

Mbed-OS possède un noyau mutitâches temps réel. C’est aec la qualité de son API C++ la raison qui nous a fait choisir cette solution pour notre enseignement.

Sans revenir sur le fonctionnement des threads, je vais détailler quelques implémentations multitâches des composants principaux de l’API.

Les threads

thread-life cycle

Instanciation d’un thread

  • Constructeur de Thread
rtos::Thread::Thread(osPriority priority = osPriorityNormal,
					uint32_t stack_size = OS_STACK_SIZE,
					unsigned char* stack_mem = NULL,
					const char* name = NULL 
					)
  • Dans un programme
Thread thread;

Communication et synchronisation de thread

Par défaut, un tread principal est créé lors de l’exécution du programme.

Le programme ci-dessous montre la création d’un second thread et la synchronisation des deux threads sur un signal reçu.(STOP_FLAG)

Thread thread2;

#define STOP_FLAG 1

void blink(DigitalOut *led) {               //blink tant que FLAG STOP_FLAG pas reçu
    while (!ThisThread::flags_wait_any_for(STOP_FLAG, 1000)) {
        *led = !*led;
    }
}
int main() {                                // thread1 start
    thread.start(callback(blink, &led1));   // thread2 start
    ThisThread::sleep_for(5000);            // thread1 attente 5s		
    thread2.flags_set(STOP_FLAG);           //thread2 envoi d’un signal (FLAG)
    thread2.join();                         //Synchronisation (et mort)
}

EventQueue

Une EventQueue est une file d’attente pour planifier des évènements.

  • L’exemple ci-dessous montre comment on peut s’en servir pour appeler des évènements périodiquements.
int main()
{
    // creates a queue with the default size
    EventQueue queue;

    // events are simple callbacks
    queue.call(printf, "called immediately\n");
    queue.call_in(2000ms, printf, "called in 2 seconds\n");
    queue.call_every(1000ms, printf, "called every 1 seconds\n");

    // events are executed by the dispatch_forever method
    queue.dispatch_forever();
}

On peut associer une EventQueue à un objet lié lui-même à un thread pour un fonctionnement multitâche de l’application.

  • Programme principal

      Thread thread_mqtt;                                             //Creation du thread
      static EventQueue event_queue_mqtt(16 * EVENTS_EVENT_SIZE);     //Creation de la l'EventQueue
    
      AdafruitMqtt myfruit(&net, event_queue_mqtt);                   //Instanciation d'un objet et association de l'EventQueue
      thread_mqtt.start(callback(&myfruit, &AdafruitMqtt::start));    //Demarrage du thread dédié, association avec l'objet et appel de la méthode start de celui-ci
    
  • Classe AdafruitMqtt

      void AdafruitMqtt::start() {
      //Appel périodique à la méthode publish et dispatch
      _id_publish = _event_queue.call_every(10s, this, &AdafruitMqtt::publish);
      _event_queue.dispatch_forever();
      }
    

Synchronisation des données

La synchronisation de données est un mécanisme qui vise à conserver la cohérence entre différentes données dans un environnement multitâche.

Les threads obtiennent le processeur selon le scheduler. Il est impossible de prévoir l’ordre d’exécution de ceux-ci. Il faut donc mettre en place des mécanismes de synchronisation ou de protection des zones de mémoires partagées.

Mutex

Mutex

Les mutex (mutual exclusion) permettent de protéger une section critique du code.

  • Exemple de mise en place d’un mutex
Thread thread1; 
Thread thread2;
Mutex battery_mutex;

void update_sensor(const char* name) {
    battery_mutex.lock();
  //Section Critique
    battery_mutex.unlock();
}

void test_thread(void const *args) {
    while (true) {
    	update_sensor((const char*)args); wait(1);
    	update_sensor((const char*)args); wait(1);
    }
}

int main() {
thread1.start(callback(test_thread, "Thread1"));
thread2.start(callback(test_thread, "Thread2"));
while (1)	{}
}

Modèle producteur/consommateur

En multitâche, on peut synchroniser les taches en attribuant des rôles aux threads Dans le modèle Producteur consommateur :

  • Le thread producteur ajoute un message dans la file et avertit de la présence du message
  • Le thread consommateur est averti de la présence du message et le consomme

Producteur/consommateur

Avec Mbed-os, on utilise le MemoryPool. Dès qu’un message est ajouté dans le pool par le thread producteur, le thread consommateur est averti et peut consommer la donnée.

  • Exemple de production et de consommation de données
typedef struct {
    //DATA
} message_t;

MemoryPool<message_t, 16> mpool;
Queue<message_t, 16> queue;

void send_thread (void) {
    while (true) {
        //PRODUCER
        queue.put(message);
        wait(1);
    }
}

int main (void) {
    thread_DATA.start(callback(send_thread));
    while (true) {
        osEvent evt = queue.get();
        if (evt.status == osEventMessage) {
            //CONSUMMER
        }
    }
}