2017-03-18 1 views
1

Je veux calculer deux choses dans deux fils où chacun d'eux a une boucle sans fin. Ces deux ne dépendent pas l'un de l'autre.Faire le même fil avec la boucle pas obtenir le même mutex encore et encore sans sommeil

De plus, je veux aussi une sortie sur le terminal dans une boucle sans fin (il suffit de recommencer chaque fois que la dernière sortie a été calculée, pas d'étape entre les deux). Par conséquent je veux des copies locales des variables globales (deux itérations consécutives du premier calcul et une valeur du deuxième calcul) toutes dans un état sûr.

Le problème est maintenant que je dois ajouter un peu de sommeil aux deux calculs pour obtenir une sortie de func1. J'ai vérifié l'utilisation du processeur et le sommeil l'abaisse définitivement. Comment puis-je contourner ce problème?

Voir aussi les commentaires pour les tests:

#include <iostream> 
#include <thread> 
#include <condition_variable> 
#include <mutex> 

int n(0); 
long s(1234); 
double t(56.78); 

std::condition_variable cond; 
std::mutex m1; 
std::mutex m2; 

void func1() { 
    while (true) { 
     // Not needed due to wait unlocking the mutex 
     //std::this_thread::sleep_for(std::chrono::nanoseconds(1)); 

     std::unique_lock<std::mutex> lck1(m1); 
     cond.wait(lck1); 
     int n1(n); 
     long s1(s); 
     cond.wait(lck1); 
     int n2(n); 
     long s2(s); 
     lck1.unlock(); 

     std::unique_lock<std::mutex> lck2(m2); 
     double ti(t); 
     lck2.unlock(); 

     // calculate and print some output 
     std::this_thread::sleep_for(std::chrono::milliseconds((n1*n2)/2)); 
     std::cout << n1 << ":" << s1 << ", " << n2 << ":" << s2 << ", " << ti << std::endl; 
    } 
} 

void func2() { 
    while (true) { 
     // Why do I need this to make func1 ever proceed (ok, really seldom and random func1 got the mutex) and 
     // how to work around this without sleep lowering time for computations? 
     std::this_thread::sleep_for(std::chrono::nanoseconds(1)); // comment out to test 

     std::unique_lock<std::mutex> lck1(m1); 

     n++; 

     // do some stuff taking some time with s 
     std::this_thread::sleep_for(std::chrono::milliseconds((n*n)/3)); 

     cond.notify_all(); 
    } 
} 

void func3() { 
    while (true) { 
     // Why do I need this to make func1 ever proceed (it got the mutex never ever) and 
     // how to work around this without sleep lowering time for computations? 
     std::this_thread::sleep_for(std::chrono::nanoseconds(1)); // comment out to test 

     std::unique_lock<std::mutex> lck2(m2); 
     // do something taking some time with t 
     std::this_thread::sleep_for(std::chrono::seconds(2)); 
    } 
} 

int main() { 

    std::thread t1(func1); 
    std::thread t2(func2); 
    std::thread t3(func3); 

    t1.join(); 
    t2.join(); 
    t3.join(); 

    return 0; 
} 
+0

Il semble que vous voulez que t3 déclenche t1 et t2, puis que t1 et t2 déclenchent t3. Vous pouvez utiliser 4 mutex, m1 pour t1, m2 pour t2, m31 et m32 pour t3. t1 attend m1, fait son travail et libère m31. t1 attend m2, fait son travail et libère m32. t3 attend m31 puis m32, fait son travail, puis relâche m1 et m2. – rcgldr

+0

S'il vous plaît signaler le code runnable real buildable qui reproduit votre problème. Lisez à propos de la publication d'un [mcve]. –

+0

@rcgldr L'idée est sympa, mais elle ne m'assure pas que j'obtiens deux valeurs consécutives. Ou peut-être que je me suis trompé. –

Répondre

0

Vous pouvez remplacer le sommeil avec:

std::this_thread::yield(); 

Cela donnera l'autre fil une chance de prendre le verrou.

http://en.cppreference.com/w/cpp/thread/yield

+0

En lisant la spécification, ce serait exactement ce dont j'ai besoin, mais d'une manière ou d'une autre cela ne fonctionne pas pendant le sommeil. Aussi, je pense que cela ne ferait pas une réelle différence, car un appel de fonction (peu importe lequel) prend plus de temps que le sommeil réel. Mais alors, il ne sera jamais possible de le rendre plus efficace comme il est. Quelque chose qui garantit qu'un autre thread d'attente obtient le verrou avant le même serait parfait. –

0

Vous pouvez le faire sans une variable de condition:

long s[2] = {1234,1234}; // instead of a single value 


void func1() { 
    while (true) { 
     std::unique_lock<std::mutex> lck1(m1); 
     int n1(n); 
     long s1(s[0]); 
     long s2(s[1]); 
     lck1.unlock(); 

     int n2 = n1 + 1; 
// ...rest of func1 is unchanged... 


void func2() { 
    while (true) { 
     std::unique_lock<std::mutex> lck1(m1); 
     n++; 
     // compute s[0] 
     std::this_thread::sleep_for(std::chrono::milliseconds((n*n)/3)); 
     n++; 
     // compute s[1] 
     std::this_thread::sleep_for(std::chrono::milliseconds((n*n)/3)); 

     std::this_thread::yield(); 
    } 
} 
+0

Testé et il a un comportement très étrange. Parfois, il n'imprime qu'une seule ligne (probablement ne reviendra jamais à func1) ou il imprime la même première ligne indéfiniment sans changement (donc ne revient probablement jamais à func2). En outre, il double le stockage nécessaire dont j'ai besoin de garder un œil sur pour ne pas courir trop vite, ce qui est tout simplement plus lent. –

1

Tout d'abord, vous devez supposer que variable de condition wait les appels ne sont pas réellement attendre. Une implémentation valide (bien qu'extrême) est lk.unlock(); lk.lock();, et parfois il se comporte comme ça en raison de "réveils parasites". Vous devez vraiment associer une condition à une variable de condition et faire une boucle si la condition n'est pas remplie. Deuxièmement, si vous notifiez la variable de condition tout en maintenant le verrou sur le mutex, alors le thread en attente s'éveillera de la notification, seulement pour bloquer sur le mutex, ainsi le thread notifiant pourra alors réacquérir le mutex avant le thread en attente a une chance.

Il est donc préférable de déverrouiller le mutex avant de notifier la variable de condition.

lck1.unlock(); 
cond.notify_all(); 

Cela donne à l'attente enfilez une chance d'acquérir le mutex immédiatement, avant que le fil de la serrure adressant la notification réacquiert.

Vous pouvez ajouter std::this_thread::yield() appels entre les itérations, pour donner aux autres threads une chance d'acquérir le verrou. Cependant, ce n'est pas idéal --- tout code dans lequel vous devez explicitement faire quelque chose pour jouer avec le planificateur est une cible pour la réécriture.

+0

Merci pour l'indice avec les fausses réveils! Mais j'ai déjà eu le déverrouillage avant la notification et cela n'a absolument pas fait de différence. –