diff --git a/proton-c/bindings/cpp/src/container_impl.cpp b/proton-c/bindings/cpp/src/container_impl.cpp
index 9cec831..9e84d23 100644
--- a/proton-c/bindings/cpp/src/container_impl.cpp
+++ b/proton-c/bindings/cpp/src/container_impl.cpp
@@ -49,8 +49,97 @@
 #include <proton/reactor.h>
 #include <proton/session.h>
 
+#include <pthread.h>
+#include <unistd.h>
+
 namespace proton {
 
+/* === simple mutex as used in epoll.c. === */
+typedef pthread_mutex_t pmutex;
+static void pmutex_init(pthread_mutex_t *pm){
+  pthread_mutexattr_t attr;
+
+  pthread_mutexattr_init(&attr);
+  pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ADAPTIVE_NP);
+  if (pthread_mutex_init(pm, &attr)) {
+      throw error("pthread mutex init failure");
+  }
+}
+
+static void pmutex_finalize(pthread_mutex_t *m) { pthread_mutex_destroy(m); }
+static inline void lock(pmutex *m) { pthread_mutex_lock(m); }
+static inline void unlock(pmutex *m) { pthread_mutex_unlock(m); }
+
+/* === similar work and jobs concept as in 0.18 C++ === */
+
+namespace {
+
+void_function0 * null_func0 = NULL;
+
+struct work {
+    duration delay;
+    bool is_03;
+    union ufunc_t {
+        void_function0 *func_03;  // can't use a reference in a union
+        ufunc_t(void_function0 *f) : func_03(f) {}
+#if PN_CPP_HAS_STD_FUNCTION
+        std::function<void()> std_func;
+        ufunc_t(std::function<void()> f) : std_func(f) {}
+#endif
+        ~ufunc_t() {}
+    } func; 
+    
+    work(duration d, void_function0& f): delay(d), is_03(true), func(&f) {}
+#if PN_CPP_HAS_STD_FUNCTION
+    work(duration d, std::function<void()> f): delay(d), is_03(false), func(f) {}
+#endif
+    work() : delay(0), is_03(true), func(null_func0) {}
+    ~work() {
+#if PN_CPP_HAS_STD_FUNCTION
+        if (!is_03)
+            func.std_func.~function<void()>();
+#endif
+    }
+    work(const work &o) : delay(o.delay), is_03(o.is_03), func(null_func0) {
+        if (is_03)
+            func.func_03 = o.func.func_03;
+#if PN_CPP_HAS_STD_FUNCTION
+        else
+            new(&func.std_func) ufunc_t(o.func.std_func);
+#endif
+    }
+
+    void operator()() {
+        if (is_03) {
+            (*func.func_03)();
+        }
+#if PN_CPP_HAS_STD_FUNCTION
+        else
+            func.std_func();
+#endif
+    }
+};
+
+}
+
+class mt_scheduler {
+public:
+    mt_scheduler(container &c, reactor &r);
+    ~mt_scheduler();
+    void schedule(duration, void_function0&);
+#if PN_CPP_HAS_STD_FUNCTION
+    void schedule(duration, std::function<void()>);
+#endif
+    void tick();
+private:
+    container &container_;
+    reactor &reactor_;
+    pmutex mutex_;
+    std::vector<work> jobs_;
+    std::vector<work> delayed_jobs_;
+};
+
+
 class container::impl::handler_context {
   public:
     static handler_context& get(pn_handler_t* h) {
@@ -105,6 +194,9 @@ class container::impl::override_handler : public proton_handler
                 container_impl_.configure_server_connection(c);
             }
         }
+        else if (type == proton_event::REACTOR_QUIESCED)
+            // Always check for queued work before sleeping in reactor select()
+            container_impl_.mt_tick();
         pn_handler_dispatch(base_handler.get(), cevent, pn_event_type_t(type));
     }
 };
@@ -143,6 +235,7 @@ container::impl::impl(container& c, const std::string& id, messaging_handler *mh
     // Note: we have just set up the following handlers that see
     // events in this order: messaging_adapter, connector override,
     // the reactor's default globalhandler (pn_iohandler)
+    mt_init();
 }
 
 namespace {
@@ -159,6 +252,7 @@ void close_acceptor(acceptor a) {
 container::impl::~impl() {
     for (acceptors::iterator i = acceptors_.begin(); i != acceptors_.end(); ++i)
         close_acceptor(i->second);
+    mt_cleanup();
 }
 
 // FIXME aconway 2016-06-07: this is not thread safe. It is sufficient for using
@@ -294,7 +388,10 @@ struct timer_handler_03 : public timer_handler {
 }
 
 void container::impl::schedule(duration delay, void_function0& f) {
-    schedule(*this, delay.milliseconds(), new timer_handler_03(f));
+    if (pthread_equal(pthread_self(), running_tid))
+        schedule(*this, delay.milliseconds(), new timer_handler_03(f));
+    else
+        mt_scheduler_->schedule(delay, f);
 }
 
 #if PN_CPP_HAS_STD_FUNCTION
@@ -307,7 +404,10 @@ struct timer_handler_std : public timer_handler {
 }
 
 void container::impl::schedule(duration delay, std::function<void()> f) {
-    schedule(*this, delay.milliseconds(), new timer_handler_std(f));
+    if (pthread_equal(pthread_self(), running_tid))
+        schedule(*this, delay.milliseconds(), new timer_handler_std(f));
+    else
+        mt_scheduler_->schedule(delay, f);
 }
 #endif
 
@@ -348,6 +448,7 @@ void container::impl::configure_server_connection(connection &c) {
 }
 
 void container::impl::run() {
+    running_tid = pthread_self();
     do {
         reactor_.run();
     } while (!auto_stop_);
@@ -362,4 +463,87 @@ void container::impl::auto_stop(bool set) {
     auto_stop_ = set;
 }
 
+/*
+ * MT safe schedule. Uses reactor's self-pipe for poking the reactor
+ * thread into action.
+ *
+ * Order is lock/push/unlock, OS write (via reactor.wakup), OS read
+ * (of wakeup pipe in reactor thread), lock/consume/unlock.
+ */
+
+mt_scheduler::mt_scheduler(container &c, reactor &r) : container_(c), reactor_(r) {
+    pmutex_init(&mutex_);
+}
+        
+mt_scheduler::~mt_scheduler() {
+    pmutex_finalize(&mutex_);
+}
+
+void container::impl::mt_init() { mt_scheduler_ = new mt_scheduler(container_, reactor_); }
+
+void container::impl::mt_cleanup() { delete mt_scheduler_; }
+
+void container::impl::mt_tick() { mt_scheduler_->tick(); }
+
+void mt_scheduler::schedule(duration delay, void_function0& f) {
+    {
+        lock(&mutex_);
+        work w(delay, f);
+        if (delay.milliseconds())
+            delayed_jobs_.push_back(w);
+        else
+            jobs_.push_back(w);
+        unlock(&mutex_);
+    }
+    reactor_.wakeup();
+    // Force a poller readable.  tick() will be processed just before a subsequent call to poll().
+    // wakeup() is thread-safe in Posix, but not on Windows.
+}
+
+#if PN_CPP_HAS_STD_FUNCTION
+void mt_scheduler::schedule(duration delay, std::function<void()> f) {
+    {
+        lock(&mutex_);
+        work w(delay, f);
+        if (delay.milliseconds())
+            delayed_jobs_.push_back(w);
+        else
+            jobs_.push_back(w);
+        unlock(&mutex_);
+    }
+    reactor_.wakeup();
+}
+
+#endif
+
+void mt_scheduler::tick() {
+    lock(&mutex_);
+    if (jobs_.empty() && delayed_jobs_.empty()) {
+        unlock(&mutex_);
+        return;
+    }
+    else {
+        std::vector<work> immediate_jobs;
+        for (std::vector<work>::iterator w = delayed_jobs_.begin(); w != delayed_jobs_.end(); ++w) {
+            // do a normal non-mt schedule on the reactor's timer
+            if (w->is_03)
+                container_.schedule(w->delay, *(w->func.func_03));
+#if PN_CPP_HAS_STD_FUNCTION
+            else
+                container_.schedule(w->delay, w->func.std_func);
+#endif
+        }
+        delayed_jobs_.clear();
+        std::swap(immediate_jobs, jobs_);
+        unlock(&mutex_);
+    
+        for (std::vector<work>::iterator w = immediate_jobs.begin(); w != immediate_jobs.end(); ++w)
+            try {
+                (*w)();
+            } catch (...) {};
+    }
+    return;
+}
+
+
 }
diff --git a/proton-c/bindings/cpp/src/include/container_impl.hpp b/proton-c/bindings/cpp/src/include/container_impl.hpp
index 7443150..7e6e434 100644
--- a/proton-c/bindings/cpp/src/include/container_impl.hpp
+++ b/proton-c/bindings/cpp/src/include/container_impl.hpp
@@ -48,6 +48,7 @@ class connector;
 class acceptor;
 class url;
 class listen_handler;
+class mt_scheduler;
 
 class container::impl {
   public:
@@ -84,6 +85,9 @@ class container::impl {
     static void schedule(impl& ci, int delay, proton_handler *h);
     static void schedule(container& c, int delay, proton_handler *h);
     template <class T> static void set_handler(T s, messaging_handler* h);
+    void mt_init();
+    void mt_cleanup();
+    void mt_tick();
 
   private:
     class handler_context;
@@ -103,6 +107,8 @@ class container::impl {
     typedef std::map<std::string, acceptor> acceptors;
     acceptors acceptors_;
     bool auto_stop_;
+    mt_scheduler *mt_scheduler_;
+    pthread_t running_tid;
 };
 
 template <class T>
