2016-05-20 2 views
4

Ich muss eine thread-sichere Arbeitswarteschlange machen, die in verschiedenen Threads Arbeit haben kann, und es wird es auf einem Arbeitsthread verarbeiten. Die Arbeit kann sehr allgemein sein, also dachte ich, dass ich Lambdas mit Capture als eine gute Möglichkeit benutze, dies zu erlauben. Ich habe den folgenden Code als Starter:Ist eine Warteschlange von Lambda ein gutes Entwurfsmuster für eine Arbeitswarteschlange in C++ 11?

#include <iostream> 
#include <vector> 
#include <functional> 
typedef std::function<void()> Task; 
typedef std::vector<Task> TaskQueue; 

class Queue 
{ 
    public: 
    void flush() { 
     for (auto it : m_queue) { 
      it(); 
     } 
    } 
    // will add thread safe locks later.   
    void queue(Task task) { 
     m_queue.push_back(task); 
    } 

private: 
    TaskQueue m_queue; 
}; 
Queue q; 

class WorkMaker 
{ 
public: 

    WorkMaker(int inA) : m_a(inA) {} 

    void MakeWork() { 
     q.queue([&]{ 
      std::cout << this->m_a << std::endl; 
     }); 
    } 


private: 
    int m_a; 
}; 

int main() 
{ 
    WorkMaker w1(1); 
    WorkMaker w2(2); 
    w1.MakeWork(); 
    w2.MakeWork(); 
    q.flush(); 
    return 0; 
} 

Gibt es etwas, von Natur aus unperformant diesem Code oder die Compiler optimieren Sie es aus? Führt auch ein Lambda in ein std::function Argument nach Wert, das Lambda oder nur den Zeiger auf es kopiert?

EDIT:

glaube ich, das Problem des Speicherbesitzes lösen kann durch Shared_ptr der Verwendung und, anstatt sie in das Lambda vorbei. Betrachten Sie die folgende Änderung:

typedef std::function<void()> Task; 
typedef std::deque<Task> TaskQueue; 

class Queue 
{ 
    public: 
    void flush() { 
     while (!m_queue.empty()) { 
      auto it = m_queue.front(); 
      m_queue.pop_front(); 
      it(); 
     } 
    } 
    // will add thread safe locks later.   
    void queue(Task task) { 
     m_queue.push_back(task); 
    } 

private: 
    TaskQueue m_queue; 
}; 
Queue q; 


class WorkMaker : public std::enable_shared_from_this<WorkMaker> 
{ 
public: 

    WorkMaker(int inA) : m_a(inA) {} 
    ~WorkMaker() { std::cout << "Destroy " << m_a << std::endl; } 
    void MakeWork() { 
     std::shared_ptr<WorkMaker> self = shared_from_this(); 
     q.queue([self]{ 
      std::cout << self->m_a << std::endl; 
     }); 
    } 
    int m_a; 
}; 

int main() 
{ 
    { 
    auto w1 = std::make_shared<WorkMaker>(1); 
    auto w2 = std::make_shared<WorkMaker>(2);  
    w1->MakeWork(); 
    w2->MakeWork(); 
    } 
    q.flush(); 
    return 0; 
} 

ich die gewünschte Ausgabe zu erhalten wie:

1 
Destroy 1 
2 
Destory 2 
+0

Wenn "WorkMaker" freigegeben wird, hinterlässt es einen ungültigen Verweis innerhalb des Lambda, der abstürzen kann. Und da Sie nicht wissen, wann die Arbeit erledigt wird, wissen Sie nie, wann Sie den 'WorkMaker' freigeben können. – nwp

+0

Wenn es keine Erfassung gab oder die Erfassung durch den Wert von Literalen erfolgte, würde das Lambda immer noch vorhanden sein, wenn 'WorkMaker' zerstört wird? –

+0

Die 'TaskQueue' besitzt ein' std :: function'-Objekt, das seine eigene Kopie des Lambda-Objekts verwaltet. Diese Kopie des Lambda wird ungefähr so ​​lange sein, wie sie die 'std :: function 'besitzt, und kann daher' WorkMaker 'überleben. Daher sollte 'WorkMaker' auf die eine oder andere Weise alle ausstehenden Aufgaben aus der Warteschlange entfernen, die beim Löschen eine Referenz beibehalten. –

Antwort

1

Die std::function wird eine private Kopie des Funktionszeiger, Lambda machen oder was auch immer es bezieht. In der Regel wird diese Kopie vom Objekt std::function referenziert, sodass späteres Kopieren später vermieden wird.

Es gibt nichts besonders langsam mit std::function Objekte auf diese Weise verwenden. Sie sollten jedoch darüber nachdenken, die std::vector durch eine std::deque zu ersetzen.

+1

Ohne Pop-Front, kein Grund zur Verwendung von Deque. – Yakk

+0

Eigentlich kopiert copyinf eine 'std :: function' den zugrunde liegenden Funktor. – MikeMB

Verwandte Themen