PageRenderTime 197ms CodeModel.GetById 14ms RepoModel.GetById 2ms app.codeStats 0ms

/mordor/scheduler.h

http://github.com/mozy/mordor
C Header | 255 lines | 118 code | 40 blank | 97 comment | 8 complexity | f318e18f2747120f08fde949bd5edb8a MD5 | raw file
Possible License(s): BSD-3-Clause
  1. #ifndef __MORDOR_SCHEDULER_H__
  2. #define __MORDOR_SCHEDULER_H__
  3. // Copyright (c) 2009 - Mozy, Inc.
  4. #include <list>
  5. #include <boost/function.hpp>
  6. #include <boost/noncopyable.hpp>
  7. #include <boost/shared_ptr.hpp>
  8. #include <boost/thread/mutex.hpp>
  9. #include "thread.h"
  10. #include "thread_local_storage.h"
  11. namespace Mordor {
  12. class Fiber;
  13. /// Cooperative user-mode thread (Fiber) Scheduler
  14. /// A Scheduler is used to cooperatively schedule fibers on threads,
  15. /// implementing an M-on-N threading model. A Scheduler can either "hijack"
  16. /// the thread it was created on (by passing useCaller = true in the
  17. /// constructor), spawn multiple threads of its own, or a hybrid of the two.
  18. ///
  19. /// Hijacking and Schedulers begin processing fibers when either
  20. /// yieldTo() or dispatch() is called. The Scheduler will stop itself when
  21. /// there are no more Fibers scheduled, and return from yieldTo() or
  22. /// dispatch(). Hybrid and spawned Schedulers must be explicitly stopped via
  23. /// stop(). stop() will return only after there are no more Fibers scheduled.
  24. class Scheduler : public boost::noncopyable
  25. {
  26. public:
  27. /// Default constructor
  28. /// By default, a single-threaded hijacking Scheduler is constructed.
  29. /// If threads > 1 && useCaller == true, a hybrid Scheduler is constructed.
  30. /// If useCaller == false, this Scheduler will not be associated with
  31. /// the currently executing thread.
  32. /// @param threads How many threads this Scheduler should be comprised of
  33. /// @param useCaller If this Scheduler should "hijack" the currently
  34. /// executing thread
  35. /// @param batchSize Number of operations to pull off the scheduler queue
  36. /// on every iteration
  37. /// @pre if (useCaller == true) Scheduler::getThis() == NULL
  38. Scheduler(size_t threads = 1, bool useCaller = true, size_t batchSize = 1);
  39. /// Destroys the scheduler, implicitly calling stop()
  40. virtual ~Scheduler();
  41. /// @return The Scheduler controlling the currently executing thread
  42. static Scheduler* getThis();
  43. /// Explicitly start the Scheduler
  44. /// Derived classes should call start() in their constructor.
  45. /// It is safe to call start() even if the Scheduler is already started -
  46. /// it will be a no-op
  47. void start();
  48. /// Explicitly stop the scheduler
  49. /// This must be called for hybrid and spawned Schedulers. It is safe to
  50. /// call stop() even if the Scheduler is already stopped (or stopping) -
  51. /// it will be a no-op
  52. /// For hybrid or hijacking schedulers, it must be called from within
  53. /// the scheduler. For spawned Schedulers, it must be called from outside
  54. /// the Scheduler.
  55. /// If called on a hybrid/hijacking scheduler from a Fiber
  56. /// that did not create the Scheduler, it will return immediately (the
  57. /// Scheduler will yield to the creating Fiber when all work is complete).
  58. /// In all other cases stop() will not return until all work is complete.
  59. void stop();
  60. /// Schedule a Fiber to be executed on the Scheduler
  61. /// @param fd The Fiber or the functor to schedule, if a pointer is passed
  62. /// in, the ownership will be transfered to this scheduler
  63. /// @param thread Optionally provide a specific thread for the Fiber to run
  64. /// on
  65. template <class FiberOrDg>
  66. void schedule(FiberOrDg fd, tid_t thread = emptytid())
  67. {
  68. bool tickleMe;
  69. {
  70. boost::mutex::scoped_lock lock(m_mutex);
  71. tickleMe = scheduleNoLock(fd, thread);
  72. }
  73. if (shouldTickle(tickleMe))
  74. tickle();
  75. }
  76. /// Schedule multiple items to be executed at once
  77. /// @param begin The first item to schedule
  78. /// @param end One past the last item to schedule
  79. template <class InputIterator>
  80. void schedule(InputIterator begin, InputIterator end)
  81. {
  82. bool tickleMe = false;
  83. {
  84. boost::mutex::scoped_lock lock(m_mutex);
  85. while (begin != end) {
  86. tickleMe = scheduleNoLock(&*begin) || tickleMe;
  87. ++begin;
  88. }
  89. }
  90. if (shouldTickle(tickleMe))
  91. tickle();
  92. }
  93. /// Change the currently executing Fiber to be running on this Scheduler
  94. /// This function can be used to change which Scheduler/thread the
  95. /// currently executing Fiber is executing on. This switch is done by
  96. /// rescheduling this Fiber on this Scheduler, and yielding to the current
  97. /// Scheduler.
  98. /// @param thread Optionally provide a specific thread for this Fiber to
  99. /// run on
  100. /// @post Scheduler::getThis() == this
  101. void switchTo(tid_t thread = emptytid());
  102. /// Yield to the Scheduler to allow other Fibers to execute on this thread
  103. /// The Scheduler will not re-schedule this Fiber automatically.
  104. ///
  105. /// In a hijacking Scheduler, any scheduled work will begin running if
  106. /// someone yields to the Scheduler.
  107. /// @pre Scheduler::getThis() != NULL
  108. static void yieldTo();
  109. /// Yield to the Scheduler to allow other Fibers to execute on this thread
  110. /// The Scheduler will automatically re-schedule this Fiber.
  111. /// @pre Scheduler::getThis() != NULL
  112. static void yield();
  113. /// Force a hijacking Scheduler to process scheduled work
  114. /// Calls yieldTo(), and yields back to the currently executing Fiber
  115. /// when there is no more work to be done
  116. /// @pre this is a hijacking Scheduler
  117. void dispatch();
  118. size_t threadCount() const
  119. {
  120. return m_threadCount + (m_rootFiber ? 1 : 0);
  121. }
  122. /// Change the number of threads in this scheduler
  123. void threadCount(size_t threads);
  124. const std::vector<boost::shared_ptr<Thread> >& threads() const
  125. {
  126. return m_threads;
  127. }
  128. tid_t rootThreadId() const { return m_rootThread; }
  129. protected:
  130. /// Derived classes can query stopping() to see if the Scheduler is trying
  131. /// to stop, and should return from the idle Fiber as soon as possible.
  132. ///
  133. /// Also, this function should be implemented if the derived class has
  134. /// any additional work to do in the idle Fiber that the Scheduler is not
  135. /// aware of.
  136. virtual bool stopping();
  137. /// The function called (in its own Fiber) when there is no work scheduled
  138. /// on the Scheduler. The Scheduler is not considered stopped until the
  139. /// idle Fiber has terminated.
  140. ///
  141. /// Implementors should Fiber::yield() when it believes there is work
  142. /// scheduled on the Scheduler.
  143. virtual void idle() = 0;
  144. /// The Scheduler wants to force the idle fiber to Fiber::yield(), because
  145. /// new work has been scheduled.
  146. virtual void tickle() = 0;
  147. bool hasWorkToDo();
  148. virtual bool hasIdleThreads() const { return m_idleThreadCount != 0; }
  149. /// determine whether tickle() is needed, to be invoked in schedule()
  150. /// @param empty whether m_fibers is empty before the new task is scheduled
  151. virtual bool shouldTickle(bool empty) const
  152. { return empty && Scheduler::getThis() != this; }
  153. /// set `this' to TLS so that getThis() can get correct Scheduler
  154. void setThis() { t_scheduler = this; }
  155. private:
  156. void yieldTo(bool yieldToCallerOnTerminate);
  157. void run();
  158. /// @pre @c fd should be valid
  159. /// @pre the task to be scheduled is not thread-targeted, or this scheduler
  160. /// owns the targeted thread.
  161. template <class FiberOrDg>
  162. bool scheduleNoLock(FiberOrDg fd,
  163. tid_t thread = emptytid()) {
  164. bool tickleMe = m_fibers.empty();
  165. m_fibers.push_back(FiberAndThread(fd, thread));
  166. return tickleMe;
  167. }
  168. private:
  169. struct FiberAndThread {
  170. boost::shared_ptr<Fiber> fiber;
  171. boost::function<void ()> dg;
  172. tid_t thread;
  173. FiberAndThread(boost::shared_ptr<Fiber> f, tid_t th)
  174. : fiber(f), thread(th) {}
  175. FiberAndThread(boost::shared_ptr<Fiber>* f, tid_t th)
  176. : thread(th) {
  177. fiber.swap(*f);
  178. }
  179. FiberAndThread(boost::function<void ()> d, tid_t th)
  180. : dg(d), thread(th) {}
  181. FiberAndThread(boost::function<void ()> *d, tid_t th)
  182. : thread(th) {
  183. dg.swap(*d);
  184. }
  185. };
  186. static ThreadLocalStorage<Scheduler *> t_scheduler;
  187. static ThreadLocalStorage<Fiber *> t_fiber;
  188. boost::mutex m_mutex;
  189. std::list<FiberAndThread> m_fibers;
  190. tid_t m_rootThread;
  191. boost::shared_ptr<Fiber> m_rootFiber;
  192. boost::shared_ptr<Fiber> m_callingFiber;
  193. std::vector<boost::shared_ptr<Thread> > m_threads;
  194. size_t m_threadCount, m_activeThreadCount, m_idleThreadCount;
  195. bool m_stopping;
  196. bool m_autoStop;
  197. size_t m_batchSize;
  198. };
  199. /// Automatic Scheduler switcher
  200. /// Automatically returns to Scheduler::getThis() when goes out of scope
  201. /// (by calling Scheduler::switchTo())
  202. struct SchedulerSwitcher : public boost::noncopyable
  203. {
  204. public:
  205. /// Captures Scheduler::getThis(), and optionally calls target->switchTo()
  206. /// if target != NULL
  207. SchedulerSwitcher(Scheduler *target = NULL);
  208. /// Calls switchTo() on the Scheduler captured in the constructor
  209. /// @post Scheduler::getThis() == the Scheduler captured in the constructor
  210. ~SchedulerSwitcher();
  211. private:
  212. Scheduler *m_caller;
  213. };
  214. }
  215. #endif