2017-05-31 1 views
0

Ich fand heraus, dass dieses Stück Code funktioniert nicht wie beabsichtigt:Bedingungsvariable und #pragma pack Fehler

#pragma pack(push, 1) 

class myclass { 
protected: 
    bool mycrasher[1]; // with mycrasher[4] it works! 
    std::mutex mtx; 
    std::condition_variable cv; 

    void thread_func() { 
     std::this_thread::sleep_for(std::chrono::seconds(1)); 

     std::chrono::milliseconds timeout(1000); 
     std::unique_lock<std::mutex> l(mtx, std::defer_lock); 
     while (true) { 
      auto now = std::chrono::system_clock::now(); 
      l.lock(); 
      while (true) { 
       std::cout << "Waiting..." << std::endl; 
       auto result = cv.wait_until(l, now + timeout); 
       std::cout << "Timed out..." << std::endl; 
       if (result == std::cv_status::timeout) 
        break; 
      } 
      l.unlock(); 
     } 
    } 

public: 
    myclass() { 
     std::lock_guard<std::mutex> l(mtx); 
     std::thread *t = new std::thread(&myclass::thread_func, this); 
     t->detach(); 
    }; 

    void start() { 
     std::cout << "myclass started." << std::endl; 
     std::cout << "sizeof(std::mutex) = " << sizeof(std::mutex) << std::endl; 
     std::cout << "sizeof(std::condition_variable) = " << sizeof(std::condition_variable) << std::endl; 
    } 
}; 
#pragma pack(pop) 

int main() { 
    myclass x; 
    x.start(); 
    std::this_thread::sleep_for(std::chrono::seconds(60)); 
} 

ich den Code erwartet eine Sekunde lang auf dem cv.wait_until Anruf zu warten und dann wiederholen, aber ist einfach auf den Anruf hängt. Das Problem (intuitiv) geht weg, wenn ich die #pragma Richtlinien entferne, weil ich den Mutex und den Lebenslauf verpacke. Allerdings, wenn ich diesen Code ausführen, erhalte ich:

myclass started. 
sizeof(std::mutex) = 40 
sizeof(std::condition_variable) = 48 

mit oder ohne pragma, so scheint es, die Verpackung nicht das eigentliche Problem ist.

Darüber hinaus entdeckte ich, dass wenn ich die mycrasher Variable auf eine 4-Byte-Grenze ausrichten das Problem ebenfalls verschwindet. Ebenso, wenn ich die Variable nach der std::condition_variable cv Deklaration verschiebe, verschwindet das Problem, aber wenn es zwischen std::mutex mtx und std::condition_variable cv bewegt wird, bleibt es bestehen.

Warum hängt das Snippet auf dem cv.wait_until Anruf, wenn der Lebenslauf nicht richtig ausgerichtet ist? Ein Performance-Hit wäre zu erwarten, aber kein einfacher Stall.

Reproduziert mit g ++ 4.9.2 und g ++ 6.3 auf einem Debian 8-System.

+1

Das '#pragma pack' ist nur für * Ihre * Struktur, nicht für andere, die Sie in Ihrer Struktur verwenden. Es scheint einfach, dass Sie keine Mutexe oder Bedingungsvariablen an ungeraden Adressen haben können. Vielleicht können Sie uns sagen, warum Sie '' pragma pack' verwenden wollen, welches Problem versuchen Sie damit zu lösen, und wir können Ihnen stattdessen bei diesem Problem helfen? –

+0

@Someprogrammerdude Danke, das habe ich gerade entdeckt. Ich wollte verstehen, warum es aufhört zu arbeiten. Dies sollte Teil einer speicherintensiven Anwendung sein, so dass gepackte Daten darauf abzielen, jedes Byte zu komprimieren. – xmas79

+0

@ xmas79: Das kann fehlschlagen. Wenn Sie jedes Byte zusammendrücken, können Sie nicht zusammenhängende Daten in derselben Cache-Zeile speichern. Dies sieht wie ein weiterer Fall einer vorzeitigen Optimierung aus. – MSalters

Antwort

1

Verwenden Sie keine Verpackung, Sie sollten das nicht mit Strukturen tun, die Mutexe und Zustandsvariablen haben. Wenn Sie denken, dass Sie das wirklich brauchen, dann stimmt etwas nicht mit Ihrer Herangehensweise, da Sie normalerweise nicht viele dieser Objekte erstellen sollten und dort nicht gepackt werden sollten.

Als einfache Abhilfe, setzen Sie mycrasher unter Ihrem mtx und cv:

#pragma pack(push, 1) 
class myclass 
{ 
protected: 
    std::mutex mtx; 
    std::condition_variable cv; 
    bool mycrasher[1]; 

    void thread_func(); 
public: 
    myclass(); 
    void start(); 
}; 
#pragma pack(pop) 

Der Grund ist es höchstwahrscheinlich nicht wie bei Verpackung erwartet funktioniert aktiviert ist, weil mtx und cv auf ungerade Adresse am Ende und Einige interne Codes, die diese verwenden, haben unterschiedliche Erwartungen. Auf einigen Plattformen (zum Beispiel ARM) könnte das einfach abstürzen.

Als eine Randnotiz, hat Ihr Code nicht nur Ausrichtungsprobleme, es leckt auch ein Thread-Objekt auf Heap innerhalb Konstruktor.

+0

Danke, das habe ich schon entdeckt. Ich kann keine explizite Erwartung in der Mutex- und/oder CV-Dokumentation finden. Also, warum sollte ich das nicht mit Strukturen tun, die Mutexe und Zustandsvariablen haben **? – xmas79

+0

@ xmas79 weil es nicht normal ist, viele von ihnen zu erstellen, um das Packen zu erfordern. Diese sollten nicht in Tausenden oder Zehntausenden erstellt werden.Sie müssten den Code mit dem Debugger durchgehen, um herauszufinden, wo Daten aufgrund einer unerwarteten Ausrichtung beschädigt werden. Sie können auch versuchen, Ihr Programm ohne Optimierungen zu erstellen, wenn das Problem weiterhin besteht, dann erwarten interne Pthread-Funktionen, dass die Daten richtig ausgerichtet sind. – Pavel

+0

Also ist es nicht meine Schuld und es ist ein Bibliotheksfehler? – xmas79