This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
in204:tds:sujets:td9:part1 [2021/11/08 18:11] bmonsuez [Question 2.2] |
in204:tds:sujets:td9:part1 [2022/11/18 10:49] (current) |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Partie 1 : Manipuler un ensemble d'arguments ====== | + | ====== Création & Manipulation de Processus léger ====== |
- | ===== Question 1 ===== | + | [[in204:tds:sujets:td9|TD9]] |
- | Ecrire une fonction ''print'' qui prend un nombre libre d'entiers en paramètres et qui affiche ces entiers sur la console. | + | ===== Références===== |
- | Pour ce faire, nous vous conseillons d'autiliser la classe ''std::initializer_list<T>'' comme argument de la fonction. | + | [[http://en.cppreference.com/w/cpp/thread/thread|std::thread]] |
- | Un [[https://en.cppreference.com/w/cpp/utility/initializer_list|std::initializer_list<T>]] permet d'accéder à la une liste de valeurs de type ''T'' qui est écrit sous la forme suivante : | + | ===== Question n°1 ===== |
+ | |||
+ | Créer un processus léger qui est associé à une fonction simple. | ||
<code cpp> | <code cpp> | ||
- | std::initializer_list<int> list_of_values = {1, 3, 4, 2 }; | + | #include<iostream> |
- | </code> | + | #include<thread> |
- | Cette liste de valeur n'est pas modifiable. Il est seulement possible de la lire en lecture comme un containeur classique. Les fonctions ''begin()'', ''end()'' ainsi que ''size()'' permettent d'accéder aux valeurs stockées dans la liste. | + | void simple_method() |
+ | { | ||
+ | int i = 5; | ||
+ | int x = 10; | ||
+ | int result = i * x; | ||
+ | std::cout << "This code calculated the value " | ||
+ | << result << " from thread ID: " | ||
+ | << std::this_thread::get_id() << "\n"; | ||
+ | } | ||
- | <hidden Correction> | ||
- | <code cpp> | + | int main() |
- | void print(std::initializer_list<int> arguments) | + | |
{ | { | ||
- | auto it = arguments.begin(), | + | std::thread simpleThread(&simple_method); |
- | end_it = arguments.end(); | + | std::cout << "Main thread is executing and waiting\n"; |
- | if (it != end_it) | + | simpleThread.join(); |
- | { | + | std::cout << "Alternate thread has terminated.\n"; |
- | std::cout << *it; | + | return 0; |
- | while (++it != end_it) | + | |
- | std::cout << ", " << *it; | + | |
- | } | + | |
} | } | ||
+ | |||
</code> | </code> | ||
- | </hidden> | + | Exécuter le code et analyser la sortie. Commenter celle-ci, notamment au regard de la documentation de la classe [[http://en.cppreference.com/w/cpp/thread/thread|std::thread]]. |
- | ===== Question 2 ===== | + | <hidden Correction> |
- | Généraliser cette fonction à d'autres types que les types entiers. | + | Un objet ''std::thread'' correspondant à un processus léger est créé et est associé au code la méthode ''simple_method''. Ce processus démarre immédiatement et lance le calcul. L'exécution du code principal se poursuit et le message ''Main thread is executing and waiting'' est affiché. La ligne suivant attend que l'exécution du processus léger associé à l'objet ''std::thread'' termine. Une fois que ce processus a terminé, l'exécution du processus principal continue, affiche le message ''Alternate thread has terminated'' et rend la main. |
+ | </hidden> | ||
- | Cette fois-ci, nous attaquons les choses un peu plus complexes. En effet, pour cela, vous allez utiliser les [[https://en.cppreference.com/w/cpp/language/parameter_pack|packs de paramètres]] des fonctions et des classes templatées. | ||
- | Quand nous écrivons : | + | ===== Question n°2 ===== |
- | * ''template<class ...Args> f(Args... arguments)'', cela signifie que la fonction prend comme arguments un ensemble d'arguments qui ont des types différents. La variable ''arguments'' fait référence à l'ensemble des valeurs ayant chacun un type différent, c'est pour cela que l'on parle de **pack de paramètres**. Attention, il n'est pas possible d'accéder aux données individuelles présentes dans le **pack de paramètres**. | + | Ecrire un programme qui lance les deux calculs suivants en parallèle, le premier dans un processus léger secondaire, le premier dans le processus léger principal : |
- | * l'opérateur ''sizeof...(Args)'' permet d'obtenir le nombres d'arguments passés à la fonction ou spécifié comme paramètres de la classe. | ||
- | |||
- | * En fait, les packs de paramètres servent à être passés directement à une fonction. Ainsi nous pouvons écrire le code suivant : | ||
<code cpp> | <code cpp> | ||
- | void number_of_args() { std::cout << "no arguments"; } | + | void worker_process(int numberOfIterations) |
- | template<classT> void number_of_args(T) { std::cout << "one argument"; } | + | |
- | template<classT1, class T2> void number_of_args(T1, T2) { std::cout << "two arguments"; } | + | |
- | + | ||
- | template<class ...Args> | + | |
- | void func(Args... arguments) | + | |
{ | { | ||
- | return number_of_args(arguments...); | + | for (int i = 1; i < numberOfIterations; i++) |
+ | { | ||
+ | std::cout << "Worker Thread: " << i << "\n"; | ||
+ | } | ||
} | } | ||
</code> | </code> | ||
- | va appeller en fonction du nombre d'arguments l'une des fonctions ''number_of_args''. | ||
- | |||
- | Nous pouvons désormais imaginer une approche récursive pour compter le nombre d'arguments en implantant le code suivant : | ||
+ | et | ||
<code cpp> | <code cpp> | ||
- | template<class ...Args> | + | void main_process() |
- | int number_of_args(T first_argument, Args... arguments) | + | |
{ | { | ||
- | return 0; | + | for (int i = 1; i < 1000; i++) |
- | } | + | { |
- | template<class T, class ...Args> | + | std::cout << "Primary Thread: " << i << "\n"; |
- | int number_of_args(T first_argument, Args... arguments) | + | } |
- | { | + | |
- | return 1 + number_of_args(arguments); | + | |
} | } | ||
</code> | </code> | ||
+ | |||
+ | Tester le code. | ||
+ | |||
+ | <hidden Correction> | ||
+ | |||
+ | Nous pouvons écrire le code suivant qui crée deux processus légers, le premier exécutant la fonction ''worker_process'' avec comme paramètre ''10000'' et le second exécutant la fonction ''main_proc''. | ||
- | L'appel de la fonction ''number_of_args'' qui suit : | ||
<code cpp> | <code cpp> | ||
- | std::cout << number_of_args(1, "e", 2.0, 'c') << "\n"; | + | int main() |
+ | { | ||
+ | std::thread worker_proc(&worker_process, 10000); | ||
+ | std::thread main_proc(&main_process); | ||
+ | worker_proc.join(); | ||
+ | main_proc.join(); | ||
+ | } | ||
</code> | </code> | ||
- | retournera ''4''. | + | L'exécution commence par la tâche ''worker_proc'' et nous devons voir certains messages de la tâche ''main_proc'', nous voyons cependant que les messages ne sont pas très entrelacés, ce qui est du au fait que l'on alloue un temps d'utilisation de la console à chacun des deux processus légers. |
- | + | ||
- | ==== Question 2.1 ==== | + | |
- | Expliquer comment le code a été exécuté ? | + | Pour mettre un peu d'entrelacement, nous pouvons ajouter un peu de temps entre les affichaches, par exemple 1ns pour le premier processus et 10ns pour le second. Ceci nous donne le code suivant: |
- | <hidden Correction> | + | <code cpp> |
- | le code ''number_of_args(1, "e", 2.0, 'c')'' correspond à appeller la fonction : | + | #include<iostream> |
+ | #include<thread> | ||
+ | #include <chrono> | ||
- | ''number_of_args<int, const char*, double, char> number_of_args(1, "e", 2.0, 'c')'' avec : | ||
- | * T = int, | ||
- | * Args.. = const char*, double, char | ||
- | Cette fonction appelle récursivement ''1 + number_of_args("e", 2.0, 'c')'', soit | ||
- | ''number_of_args<const char*, double, char> number_of_args("e", 2.0, 'c')'' avec : | + | void worker_process(int numberOfIterations) |
- | * T = const char*, | + | { |
- | * Args.. = double, char | + | for (int i = 1; i < numberOfIterations; i++) |
- | + | { | |
- | puis ensuite ''1 + number_of_args(2.0, 'c')'', soit | + | std::cout << "Worker Thread: " << i << "\n"; |
- | + | std::this_thread::sleep_for(10ns); | |
- | ''number_of_args<double, char> number_of_args("e", 2.0, 'c')'' avec : | + | } |
- | * T = double, | + | } |
- | * Args.. = char | + | |
- | + | ||
- | puis ensuite ''1 + number_of_args('c')'', soit | + | |
- | + | ||
- | ''number_of_args<char> number_of_args("e", 2.0, 'c')'' avec : | + | |
- | * T = char, | + | |
- | * Args.. = | + | |
- | + | ||
- | puis ensuite ''1 + number_of_args()'', soit | + | |
- | + | ||
- | ''number_of_args<char> number_of_args()'' avec : | + | |
- | * Args.. = | + | |
- | + | ||
- | qui elle retroune ''0''. | + | |
- | + | ||
- | Ce qui génère bien le résultat ''4''. | + | |
- | + | ||
- | </hidden> | + | |
- | + | ||
- | ==== Question 2.2 ==== | + | |
- | + | ||
- | En vous inspirant du code précédant, proposer une fonction ''print'' qui prend un nombre variable de paramètres qui peuvent avoir des types différents et qui imprime cette liste de paramètres sur la console. | + | |
- | + | ||
- | <hidden Correction> | + | |
- | template<class T> | + | void main_process() |
- | void print(T first_argument) | + | |
{ | { | ||
- | std::cout << first_argument; | + | for (int i = 1; i < 1000; i++) |
+ | { | ||
+ | using namespace std; | ||
+ | std::cout << "Primary Thread: " << i << "\n"; | ||
+ | std::this_thread::sleep_for(1ns); | ||
+ | } | ||
} | } | ||
- | template<class T, class ...Args> | + | int main() |
- | void print(T first_argument, Args... arguments) | + | |
{ | { | ||
- | std::cout << first_argument; | + | std::thread worker_proc(&worker_process, 10000); |
- | if (sizeof...(Args) > 0) | + | std::thread main_proc(&main_process); |
- | std::cout << ", "; | + | worker_proc.join(); |
- | print(arguments...); | + | main_proc.join(); |
} | } | ||
+ | </code> | ||
</hidden> | </hidden> | ||
- | |||
- | |||
- | ====== Navigation ====== | ||
- | |||
- | [[.part2|Partie suivante: Mesurer le temps passé par une fonction]] | ||