2016-05-13 11 views
9

Ich habe zwei Funktionen foo und bar, die sich gegenseitig ausschließen sollten, da sie auf den gleichen Daten arbeiten. Allerdings foo dupliziert viel Code von bar, so würde ich gerne foo refactor um einen Anruf an bar zu machen.Sich gegenseitig ausschließende Funktionen, die sich gegenseitig aufrufen

Das ist ein Problem, denn dann kann ich keinen einzigen Mutex für beide Funktionen verwenden, weil dann foo bei Aufruf bar Deadlock würde. Also statt "sich gegenseitig ausschließen" möchte ich nur "sich gegenseitig von verschiedenen Threads ausschließen".

Gibt es ein Muster für die Umsetzung? Ich benutze C++ und ich bin in Ordnung mit C++ 14/boost, wenn ich etwas wie shared_mutex brauche.

+0

Unter Verwendung eines 'std :: mutex' üerhaps? –

+0

Übergeben Sie den Mutex als Parameter an den refaktorierten Abschnitt des gemeinsam genutzten Codes. –

+2

@ πάνταῥεῖ: "ähmaps" klingt wie eine Krankheit, die du aus dem Urlaub mit nach Hause bringst, oder vielleicht von einem Metalkonzert. –

Antwort

20

Definieren Sie eine private „unlocked“ Funktion und verwenden, die von beiden foo und bar:

void bar_unlocked() 
{ 
    // assert that mx_ is locked 
    // real work 
} 

void bar() 
{ 
    std::lock_guard<std::mutex> lock(mx_); 
    bar_unlocked(); 
} 

void foo() 
{ 
    std::lock_guard<std::mutex> lock(mx_); 
    // stuff 
    bar_unlocked(); 
    // more stuff 
} 
+0

Ja, viel bessere Lösung als schreckliche rekursive Mutex. – SergeyA

+0

ganz offensichtlich (hatte die gleiche Idee, aber war hier zu spät) – Walter

4

eine andere Art und Weise - dies den Vorteil, dass Sie nachweisen können, dass das Schloss genommen wurde:

void bar_impl(std::unique_lock<std::mutex> lock) 
{ 
    assert(lock.owns_lock()); 
    // real work 
} 

void bar() 
{ 
    bar_impl(std::unique_lock<std::mutex>(mx_)); 
} 

void foo() 
{ 
    // stuff 
    bar_impl(std::unique_lock<std::mutex>(mx_)); 
    // more stuff 
} 

Begründung:

std::mutex ist beweglich nicht (von der Norm seinem Mandat), aber ein std::unique_lock<std::mutex> ist. Aus diesem Grund können wir eine Sperre in einen Angerufenen verschieben und bei Bedarf an einen Anrufer zurücksenden.

Dies ermöglicht es uns, das Eigentum an der Sperre in jeder Phase einer Aufrufkette nachzuweisen.

Sobald der Optimierer involviert ist, ist es wahrscheinlich, dass alle Lock-Moving-Vorgänge entfernt werden. Dies gibt uns das Beste aus beiden Welten - beweisbares Eigentum und maximale Leistung.

Ein vollständigeres Beispiel:

#include <mutex> 
#include <cassert> 
#include <functional> 

struct actor 
{ 
    // 
    // public interface 
    // 

    // perform a simple synchronous action 
    void simple_action() 
    { 
    impl_simple_action(take_lock()); 
    } 

    /// perform an action either now or asynchronously in the future 
    /// hander() is called when the action is complete 
    /// handler is a latch - i.e. it will be called exactly once 
    /// @pre an existing handler must not be pending 
    void complex_action(std::function<void()> handler) 
    { 
    impl_complex_action(take_lock(), std::move(handler)); 
    } 

    private: 

    // 
    // private external interface (for callbacks) 
    // 
    void my_callback() 
    { 
    auto lock = take_lock(); 
    assert(!_condition_met); 
    _condition_met = true; 
    impl_condition_met(std::move(lock)); 
    } 


    // private interface 

    using mutex_type = std::mutex; 
    using lock_type = std::unique_lock<mutex_type>; 

    void impl_simple_action(const lock_type& lock) 
    { 
    // assert preconditions 
    assert(lock.owns_lock()); 
    // actions here 
    } 

    void impl_complex_action(lock_type my_lock, std::function<void()> handler) 
    { 
    _handler = std::move(handler); 
    if (_condition_met) 
    { 
     return impl_condition_met(std::move(my_lock)); 
    } 
    else { 
     // initiate some action that will result in my_callback() being called 
     // some time later 
    } 
    } 

    void impl_condition_met(lock_type lock) 
    { 
     assert(lock.owns_lock()); 
     assert(_condition_met); 
     if(_handler) 
     { 
     _condition_met = false; 
     auto copy = std::move(_handler); 
     // unlock here because the callback may call back into our public interface 
     lock.unlock(); 
     copy(); 
     } 
    } 



    auto take_lock() const -> lock_type 
    { 
    return lock_type(_mutex); 
    } 


    mutable mutex_type _mutex; 

    std::function<void()> _handler = {}; 
    bool _condition_met = false; 
}; 

void act(actor& a) 
{ 
    a.complex_action([&a]{ 
    // other stuff... 
    // note: calling another public interface function of a 
    // during a handler initiated by a 
    // the unlock() in impl_condition_met() makes this safe. 
    a.simple_action(); 
    }); 

} 
+0

Ist das 'mehr Zeug' immer noch mit dem Mutex gesperrt? 'Verschieben Sie eine Sperre in einen Aufgerufenen und geben Sie sie an einen Aufrufer zurück. Eine weitere Option ist das Übergeben der Referenz. – jingyu9575

Verwandte Themen