Nouvelles Du Monde

Correction : Bug dans le planificateur de priorités pour les coroutines dans le blog C++

Correction : Bug dans le planificateur de priorités pour les coroutines dans le blog C++

2023-12-11 11:25:00

Dans mes deux derniers articles de blog, j’ai introduit un planificateur de priorités pour les coroutines. Le code correspondant contenait une erreur.

Publicité


Rainer Grimm travaille depuis de nombreuses années en tant qu’architecte logiciel, responsable d’équipe et de formation. Il aime écrire des articles sur les langages de programmation C++, Python et Haskell, mais aime également intervenir fréquemment lors de conférences spécialisées. Sur son blog Modern C++, il parle intensément de sa passion C++.



Voici à quoi ressemble le planificateur défectueux :

// priority_queueSchedulerPriority.cpp

#include 
#include 
#include 
#include 
#include 
#include 


struct Task {

  struct promise_type {
    std::suspend_always initial_suspend() noexcept { return {}; }
    std::suspend_always final_suspend() noexcept { return {}; }

    Task get_return_object() { 
        return std::coroutine_handle::from_promise(*this); 
    }
    void return_void() {}
    void unhandled_exception() {}
  };

  Task(std::coroutine_handle handle): handle{handle}{}

  auto get_handle() { return handle; }

  std::coroutine_handle handle;
};

using job = std::pair>;

template                    
requires std::invocable &&                // (2)
         std::predicate             
class Scheduler {

  std::priority_queue, Comperator> _prioTasks;

  public: 
    void emplace(int prio, std::coroutine_handle<> task) {
      _prioTasks.push(std::make_pair(prio, task));
    }

    void schedule() {
      Updater upd = {};                                            // (3)
      while(!_prioTasks.empty()) {
        auto [prio, task] = _prioTasks.top();
        _prioTasks.pop();
        task.resume();

        if(!task.done()) { 
          _prioTasks.push(std::make_pair(upd(prio), task));          // (4)
        }
        else {
          task.destroy();
        }
      }
    }

};


Task createTask(const std::string& name) {
  std::cout << name << " startn";
  co_await std::suspend_always();
  for (int i = 0; i <= 3; ++i ) { 
    std::cout << name << " execute " << i << "n";                  // (5)
    co_await std::suspend_always();
  }
  co_await std::suspend_always();
  std::cout << name << " finishn";
}


int main() {

  std::cout << 'n';

  Scheduler scheduler1;                                               // (6)

  scheduler1.emplace(0, createTask("TaskA").get_handle());
  scheduler1.emplace(1, createTask("  TaskB").get_handle());
  scheduler1.emplace(2, createTask("    TaskC").get_handle());

  scheduler1.schedule();

  std::cout << 'n';

  Scheduler scheduler2;        // (7)

  scheduler2.emplace(0, createTask("TaskA").get_handle());
  scheduler2.emplace(1, createTask("  TaskB").get_handle());
  scheduler2.emplace(2, createTask("    TaskC").get_handle());

  scheduler2.schedule();

  std::cout << 'n';

}

Voici le résultat du programme que j'ai reçu :



Christof Meerwald a reçu une édition différente auprès du GCC. Merci pour cette astuce. Voici la sortie GCC avec l'optimisation activée.



La sortie Windows était également incorrecte :



Voici les lignes cruciales avec l'erreur :

Task createTask(const std::string& name) {                  // (1)
  std::cout << name << " startn";
  co_await std::suspend_always();
  for (int i = 0; i <= 3; ++i ) { 
    std::cout << name << " execute " << i << "n";                  
    co_await std::suspend_always();
  }
  co_await std::suspend_always();
  std::cout << name << " finishn";
}


int main() {

  std::cout << 'n';

  Scheduler scheduler1;                                               

  scheduler1.emplace(0, createTask("TaskA").get_handle());    // (2)
  scheduler1.emplace(1, createTask("  TaskB").get_handle());  // (3)
  scheduler1.emplace(2, createTask("    TaskC").get_handle());// (4)

  scheduler1.schedule();

  std::cout << 'n';

  Scheduler scheduler2;        

  scheduler2.emplace(0, createTask("TaskA").get_handle());    // (5)
  scheduler2.emplace(1, createTask("  TaskB").get_handle());  // (6)
  scheduler2.emplace(2, createTask("    TaskC").get_handle());// (7)

  scheduler2.schedule();

  std::cout << 'n';

}

La Coroutine createTask prend sa chaîne comme référence const lvalue (1), mais ses arguments "TaskA" - "TaskC" sont des rvalues ​​​​(2 - 7). L'utilisation d'une référence à une variable temporaire est un comportement indéfini. Les autres planificateurs priority_SchedulerSimplified et priority_queueSchedulerComparator dans les articles « Langage de programmation C++ : Un planificateur de priorités pour les coroutines » et «Langage de programmation C++ : un planificateur de priorités sophistiqué pour les coroutines" J'ai le même problème.

Résoudre le problème est facile. Soit prend la coroutine createTask ihr Argument par valeur an (Task createTask(std::string name)) ou leurs arguments deviennent des lvalues. Voici la deuxième approche en (1) - (3) :

// priority_queueSchedulerPriority.cpp

#include 
#include 
#include 
#include 
#include 
#include 


struct Task {

  struct promise_type {
    std::suspend_always initial_suspend() noexcept { return {}; }
    std::suspend_always final_suspend() noexcept { return {}; }

    Task get_return_object() { 
        return std::coroutine_handle::from_promise(*this); 
    }
    void return_void() {}
    void unhandled_exception() {}
  };

  Task(std::coroutine_handle handle): handle{handle}{}

  auto get_handle() { return handle; }

  std::coroutine_handle handle;
};

using job = std::pair>;

template                    
requires std::invocable &&                
         std::predicate             
class Scheduler {

  std::priority_queue, Comperator> _prioTasks;

  public: 
    void emplace(int prio, std::coroutine_handle<> task) {
      _prioTasks.push(std::make_pair(prio, task));
    }

    void schedule() {
      Updater upd = {};                                            
      while(!_prioTasks.empty()) {
        auto [prio, task] = _prioTasks.top();
        _prioTasks.pop();
        task.resume();

        if(!task.done()) { 
          _prioTasks.push(std::make_pair(upd(prio), task));          
        }
        else {
          task.destroy();
        }
      }
    }

};


Task createTask(const std::string& name) {
  std::cout << name << " startn";
  co_await std::suspend_always();
  for (int i = 0; i <= 3; ++i ) { 
    std::cout << name << " execute " << i << "n";                  
    co_await std::suspend_always();
  }
  co_await std::suspend_always();
  std::cout << name << " finishn";
}


int main() {

  std::cout << 'n';

  std::string taskA = "TaskA";                    // (1)
  std::string taskB = "  TaskB";                  // (2)
  std::string taskC = "    TaskC";                // (3)

  Scheduler scheduler1;                                          

  scheduler1.emplace(0, createTask(taskA).get_handle());
  scheduler1.emplace(1, createTask(taskB).get_handle());
  scheduler1.emplace(2, createTask(taskC).get_handle());

  scheduler1.schedule();

  std::cout << 'n';

  Scheduler scheduler2;        

  scheduler2.emplace(0, createTask(taskA).get_handle());
  scheduler2.emplace(1, createTask(taskB).get_handle());
  scheduler2.emplace(2, createTask(taskC).get_handle());

  scheduler2.schedule();

  std::cout << 'n';

}

Les coroutines offrent un moyen intuitif d'écrire du code asynchrone. Mon prochain article sera un article invité de Ljubic Damir présentant un workflow mono-producteur - mono-consommateur basé sur des coroutines.


(moi)

Vers la page d'accueil



#Correction #Bug #dans #planificateur #priorités #pour #les #coroutines #dans #blog
1702325487

Facebook
Twitter
LinkedIn
Pinterest

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

ADVERTISEMENT