Policy benutzen, um Threading-Verhalten in Klasse zu geben



  • Wo ich gerade schonmal beim Refaktoren bin 😉 würde ich gerne folgendes häßliche Konstrukt eliminieren:

    class Foo {
    public:
        void bar() {
    #ifdef THREADING_MULTI
            boost::mutex::scoped_lock lock(mutex_);
    #endif
            doSomething;
        }
    private:
    #ifdef THREADING_MULTI
        boost::mutex mutex_;
    #endif
    };
    

    Ich erinnere mich dunkel, im Alexandrescu genau so ein Beispiel gelesen zu haben, wo man über eine Policy sagen kann, dass man die Klasse jetzt gerne multi- oder singlethreaded hätte. Ist aber schon wieder ne Weile her, dass ich den gelesen habe, und greifbar habe ich ihn auch gerade nicht.

    Wer hilft meinem Gedächtnis mal kurz auf die Sprünge?

    Philipp



  • Mach ein Template Argument, welches die ThreadingPolicy ist.
    Im fall von Multi Threaded sind dort z.B. Methoden drin mit lock(..) / unlock(..) etc. Im Fall von Single Threaded sind dort die impl. einfach leer.



  • Hab da mal was gebastelt.

    Folgendes ist aber Quark, weil scoped_lock natürlich nicht funktioniert. Den RAII-Komfort von scoped_lock will ich aber nicht verlieren. Wie bringe ich das in der Policy unter?

    #include <iostream>
    #include <boost/thread.hpp>
    using namespace std;
    
    class BoostThreader {
    public:
        typedef boost::mutex Mutex;
        void lock(Mutex& the_lock) {
            boost::mutex::scoped_lock scoped_lock(the_lock);
        }
    };
    
    class SingleThreaded {
    public:
        typedef void* Mutex;
        void lock(Mutex&) {}
    };
    
    template <class ThreadingPolicy>
    class Foo: public ThreadingPolicy {
    public:
        typedef typename ThreadingPolicy::Mutex Mutex;
        using ThreadingPolicy::lock;
        void bar() {
            lock(lock_);
            cout << "Bar!" << endl;
        }
    private:
        Mutex lock_;
    };
    
    int main(int argc, char *argv[])
    {
        Foo<BoostThreader> my_threaded_foo;
        my_threaded_foo.bar();
    
        Foo<SingleThreaded> my_singlethreaded_foo;
        my_singlethreaded_foo.bar();
    
        return 0;
    }
    

  • Administrator

    Wieso erbst du von der Policy? Und wieso überhaupt so kompliziert? 🙂
    Grundsätzlich musst du nur eine neue Mutex-Klasse erstellen, welche das Lockable Konzept von Boost unterstützt, aber tatsächlich nichts tut:

    struct no_mutex
    {
      void lock() { }
      void unlock() { }
      bool try_lock() { return true; }
    };
    

    Und nun zu deiner Foo-Klasse:

    template<typename ThreadingMutexT>
    class Foo
    {
    private:
      ThreadingMutexT m_mutex;
    
    public:
      void bar()
      {
        boost::lock_guard<ThreadingMutexT> guard(m_mutex);
      }
    };
    

    Und das ganze in der Anwendung:

    int main()
    {
      Foo<boost::mutex> mutex_foo;
      Foo<no_mutex> no_mutex_foo;
    
      mutex_foo.bar();
      no_mutex_foo.bar();
    
      return 0;
    }
    

    Grüssli



  • Das Problem ist, dass ich boost komplett wegkapseln muss, da der single-threaded teil des projekts kein boost zur verfügung hat.

    Daher darf in der Klasse kein boost-Code drinstehen, der muß über die Policy reinkommen. Das ging vorher halt über ein #ifdef.


  • Administrator

    Dann schreib halt eben deinen eigenen Lock-Guard. Ist ja auch nicht weiters wild:

    template<typename MutexT>
    class scoped_lock
    {
    private:
      MutexT& m_mutex;
    
    public:
      scoped_lock(Mutex& mutex)
       : m_mutex(mutex)
      {
        m_mutex.lock();
      }
    
      ~scoped_lock()
      {
        m_mutex.unlock();
      }
    
    private:
      scoped_lock(scoped_lock const&) { }
      scoped_lock& operator =(scoped_lock const&) { }
    };
    

    Statt boost::lock_guard verwendest du halt nun scoped_lock .

    Grüssli



  • Das scheint genau das zu leisten, was ich brauche:

    #include <iostream>
    #include <boost/thread.hpp>
    #include <QMutexLocker>
    using namespace std;
    
    class BoostThreaded {
    public:
        typedef boost::mutex Mutex;
        typedef boost::mutex::scoped_lock ScopedLock;
    protected:
        Mutex lock_;
    };
    
    class QThreaded {
    public:
        typedef QMutex Mutex;
        typedef QMutexLocker ScopedLock;
        QThreaded():lock_(new QMutex) {};
        ~QThreaded() { delete lock_; }
    protected:
        Mutex* lock_;
    };
    
    class SingleThreaded {
    public:
        typedef void* Mutex;
        typedef void* ScopedLock;
    protected:
        Mutex lock_;
    };
    
    template <class ThreadingPolicy>
    class Foo: public ThreadingPolicy {
    public:
        typedef typename ThreadingPolicy::ScopedLock ScopedLock;
        using ThreadingPolicy::lock_;
        void bar() {
            ScopedLock lock(lock_);
            cout << "Bar!" << endl;
        }
    };
    
    int main(int argc, char *argv[])
    {
        Foo<BoostThreaded> my_threaded_foo;
        my_threaded_foo.bar();
    
        Foo<QThreaded> my_qthreaded_foo;
        my_qthreaded_foo.bar();
    
        Foo<SingleThreaded> my_singlethreaded_foo;
        my_singlethreaded_foo.bar();
    
        return 0;
    }
    

    Meinungen ?


  • Administrator

    Wieso erbst du von der Policy?

    Grüssli



  • Ähm, weil ich das so gelernt habe?!
    Laut Alexandrescu ist policy-based design die Kombination von Mehrfachvererbung mit Templates durch das Erben von seinen Templateparametern. Wikipedia sieht das genauso.

    Philipp

    EDIT: Und so klappts auch mit der Nachbarin, ähh, der Wait-Condition:

    #include <iostream>
    #include <boost/thread.hpp>
    #include <QMutexLocker>
    #include <QWaitCondition>
    using namespace std;
    
    class BoostThreaded {
    public:
        typedef boost::mutex Mutex;
        typedef boost::mutex::scoped_lock ScopedLock;
        typedef boost::condition_variable WaitCondition;
        void wait(ScopedLock& lock) { condition_.wait(lock); }
        void notify() { condition_.notify_one(); }
    protected:
        Mutex lock_;
        WaitCondition condition_;
    };
    
    class QThreaded {
    public:
        typedef QMutex Mutex;
        typedef QMutexLocker ScopedLock;
        typedef QWaitCondition WaitCondition;
        void wait(ScopedLock&) { condition_.wait(lock_); }
        void notify() { condition_.wakeOne(); }
        QThreaded():lock_(new QMutex) {};
        ~QThreaded() { delete lock_; }
    protected:
        Mutex* lock_;
        WaitCondition condition_;
    };
    
    class SingleThreaded {
    public:
        typedef void* Mutex;
        typedef void* ScopedLock;
        void wait(ScopedLock&) {}
        void notify() {}
    protected:
        Mutex lock_;
    };
    
    template <class ThreadingPolicy>
    class Foo: public ThreadingPolicy {
    public:
        typedef typename ThreadingPolicy::ScopedLock ScopedLock;
        using ThreadingPolicy::lock_;
        using ThreadingPolicy::wait;
        using ThreadingPolicy::notify;
        void bar() {
            ScopedLock lock(lock_);
            cout << "Bar!" << endl;
            wait(lock);
            notify();
        }
    };
    
    int main(int argc, char *argv[])
    {
        Foo<BoostThreaded> my_threaded_foo;
        my_threaded_foo.bar();
    
        Foo<QThreaded> my_qthreaded_foo;
        my_qthreaded_foo.bar();
    
        Foo<SingleThreaded> my_singlethreaded_foo;
        my_singlethreaded_foo.bar();
    
        return 0;
    }
    


  • Dravere schrieb:

    Wieso erbst du von der Policy?

    Erben muß nicht sein, und eigentlich denke ich, daß es hier böse ist.
    Aber die "Empty Member" Optimization http://www.cantrip.org/emptyopt.html greift da so schön.



  • Aber wie soll ich sonst an die mutex-membervariable drankommen? Dann müsste ich ja Foo zum friend jeder Policy machen, bzw die Policy selbst wieder zum template, damit T ihr friend sein kann, damit er auf mutex_ zugreifen kann.



  • PhilippM schrieb:

    Aber wie soll ich sonst an die mutex-membervariable drankommen? Dann müsste ich ja Foo zum friend jeder Policy machen, bzw die Policy selbst wieder zum template, damit T ihr friend sein kann, damit er auf mutex_ zugreifen kann.

    Policies sind so klein und simpel, da dard ein Attribut auch mal pubnlic sein.



  • na dann 🙂



  • Dann präsentiere ich hier die parametrierbare concurrent-queue:

    #include <iostream>
    #include <queue>
    #include <boost/thread.hpp>
    #include <QMutexLocker>
    #include <QWaitCondition>
    using namespace std;
    
    class BoostThreaded {
    public:
        typedef boost::mutex Mutex;
        typedef boost::mutex::scoped_lock ScopedLock;
        typedef boost::condition_variable WaitCondition;
        void wait(ScopedLock& lock) { condition_.wait(lock); }
        void notify() { condition_.notify_one(); }
        Mutex mutex_;
        WaitCondition condition_;
    };
    
    class QThreaded {
    public:
        typedef QMutex Mutex;
        typedef QMutexLocker ScopedLock;
        typedef QWaitCondition WaitCondition;
        void wait(ScopedLock&) { condition_.wait(mutex_); }
        void notify() { condition_.wakeOne(); }
        QThreaded():mutex_(new QMutex) {};
        ~QThreaded() { delete mutex_; }
        Mutex* mutex_;
        WaitCondition condition_;
    };
    
    class SingleThreaded {
    public:
        typedef void* Mutex;
        typedef void* ScopedLock;
        void wait(ScopedLock&) {}
        void notify() {}
        Mutex mutex_;
    };
    
    template <class ThreadingPolicy>
    class Foo {
    public:
        typedef typename ThreadingPolicy::ScopedLock ScopedLock;
    
        void bar() {
            ScopedLock lock(tp.mutex_);
            cout << "Bar!" << endl;
        }
    private:
        ThreadingPolicy tp;
    };
    
    template<typename Data, class ThreadingPolicy>
    class concurrent_queue
    {
    public:
        void push(Data const& data)
        {
            {
                ScopedLock lock(tp.mutex_);
                the_queue.push(data);
            }
            tp.notify();
        }
    
        bool empty() const
        {
            ScopedLock lock(tp.mutex_);
            return the_queue.empty();
        }
    
        bool try_pop(Data& popped_value)
        {
            ScopedLock lock(tp.mutex_);
            if(the_queue.empty())
            {
                return false;
            }
    
            popped_value = the_queue.front();
            the_queue.pop();
            return true;
        }
    
        void wait_and_pop(Data& popped_value)
        {
            ScopedLock lock(tp.mutex_);
            while(the_queue.empty())
            {
                wait(mutex_);
            }
    
            popped_value=the_queue.front();
            the_queue.pop();
        }
    private:
        typedef typename ThreadingPolicy::ScopedLock ScopedLock;
        std::queue<Data> the_queue;
        ThreadingPolicy tp;
    };
    
    int main(int, char **)
    {
        Foo<BoostThreaded> my_threaded_foo;
        my_threaded_foo.bar();
    
        Foo<QThreaded> my_qthreaded_foo;
        my_qthreaded_foo.bar();
    
        Foo<SingleThreaded> my_singlethreaded_foo;
        my_singlethreaded_foo.bar();
    
        concurrent_queue<float, BoostThreaded> my_concurrent_float_queue;
        my_concurrent_float_queue.push(23.42f);
    
        float f;
        my_concurrent_float_queue.wait_and_pop(f);
        cout << f << endl;
    
        return 0;
    }
    

Anmelden zum Antworten