Bonjour,
J'ai une fonction qui traite les éléments d'une collection en itérant dessus par extraction de ses éléments un par un.
Voici un exemple de code simplifié:
J'aimerais améliorer les performances de ce code sachant que le traitement de chacun des éléments de la collection est indépendant. Sur base du code qui précède il me suffirait d'appeler la méthode void MyRunner::run() dans autant de threads que voulu.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 struct MyData { }; class MyRunner { std::vector<MyData> f_md_vect; public: void init() { // fill f_md_vect with data } void run(); }; void MyRunner::run() { for(auto & md : f_md_vect) { //... traitement d'un élément de la collection ici } } void mainST() { MyRunner mr; mr.init(); mr.run(); }
Le problème est alors de s'assurer que chaque élément de la collection ne soit traité qu'une seule fois, donc de verrouiller d'une manière ou d'une autre l'itération qui a lieu dans le code for(auto & md : f_md_vect). En d'autres termes, il faudrait extraire l'itérateur de la boucle for et en faire une variable partagée entre les threads.
Voici une solution:
Cette solution fonctionne et je l'ai implémentée.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 struct MyData { }; class MyRunnerMT { std::vector<MyData> f_md_vect; std::vector<MyData>::iterator f_md_vect_it{}; // null iterator ... (?) std::shared_mutex shmtx; MyData *next() { std::lock_guard<std::shared_mutex> lock{shmtx}; if (f_md_vect_it == f_md_vect.cend()) f_md_vect_it = std::vector<MyData>::iterator{}; // null iterator ... (?) if (f_md_vect_it == std::vector<MyData>::iterator{}) return nullptr; return &*f_md_vect_it++; } public: void init() { // fill f_md_vect with data if (f_md_vect.size()) f_md_vect_it = f_md_vect.begin(); } void run(); }; void MyRunnerMT::run() { for (MyData *mdptr; (mdptr = next()) != nullptr;) { MyData & md = *mdptr; //... traitement d'un élément de la collection ici } } void mainMT() { MyRunnerMT mr; mr.init(); std::future<void> f1 = std::async(&MyRunnerMT::run, &mr); std::future<void> f2 = std::async(&MyRunnerMT::run, &mr); std::future<void> f3 = std::async(&MyRunnerMT::run, &mr); std::future<void> f4 = std::async(&MyRunnerMT::run, &mr); f1.wait(); f2.wait(); f3.wait(); f4.wait(); }
Mais je me demandais s'il n'y avait pas plus simple et plus lisible.
Citons ce qui me déplaît:
-exposer aussi clairement un pointeur (retour de méthode next());
-initialiser un itérateur avec la valeur "nulle" et en faire la valeur de fin d'itération;
-le déréférencement de l'itérateur pour en extraire le pointer sous-jacent (&*);
-utiliser un mutex alors que l'itérateur pourrait être atomic (mais je ne vois pas comment faire).
Merci pour vos idées
Partager