You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

150 lines
3.1 KiB

  1. #pragma once
  2. #include "bounded_queue.hpp"
  3. #include "make_unique.hpp"
  4. #include <atomic>
  5. #include <csignal>
  6. #include <functional>
  7. #include <future>
  8. #include <memory>
  9. #include <string>
  10. #include <thread>
  11. #include <tuple>
  12. #include <type_traits>
  13. #include <utility>
  14. #include <vector>
  15. class BoundedThreadPool
  16. {
  17. private:
  18. using Proc = std::function<void(void)>;
  19. using Queue = BoundedQueue<Proc>;
  20. using Queues = std::vector<std::unique_ptr<Queue>>;
  21. public:
  22. explicit
  23. BoundedThreadPool(std::size_t const thread_count_ = std::thread::hardware_concurrency(),
  24. std::size_t const queue_depth_ = 1,
  25. std::string const name_ = {})
  26. : _queues(),
  27. _count(thread_count_)
  28. {
  29. for(std::size_t i = 0; i < thread_count_; i++)
  30. _queues.emplace_back(std::make_unique<Queue>(queue_depth_));
  31. auto worker = [this](std::size_t i)
  32. {
  33. while(true)
  34. {
  35. Proc f;
  36. for(std::size_t n = 0; n < (_count * K); ++n)
  37. {
  38. if(_queues[(i + n) % _count]->pop(f))
  39. break;
  40. }
  41. if(!f && !_queues[i]->pop(f))
  42. break;
  43. f();
  44. }
  45. };
  46. sigset_t oldset;
  47. sigset_t newset;
  48. sigfillset(&newset);
  49. pthread_sigmask(SIG_BLOCK,&newset,&oldset);
  50. _threads.reserve(thread_count_);
  51. for(std::size_t i = 0; i < thread_count_; ++i)
  52. _threads.emplace_back(worker, i);
  53. if(!name_.empty())
  54. {
  55. for(auto &t : _threads)
  56. pthread_setname_np(t.native_handle(),name_.c_str());
  57. }
  58. pthread_sigmask(SIG_SETMASK,&oldset,NULL);
  59. }
  60. ~BoundedThreadPool()
  61. {
  62. for(auto &queue : _queues)
  63. queue->unblock();
  64. for(auto &thread : _threads)
  65. pthread_cancel(thread.native_handle());
  66. for(auto &thread : _threads)
  67. thread.join();
  68. }
  69. template<typename F>
  70. void
  71. enqueue_work(F&& f_)
  72. {
  73. auto i = _index.fetch_add(1,std::memory_order_relaxed);
  74. for(std::size_t n = 0; n < (_count * K); ++n)
  75. {
  76. if(_queues[(i + n) % _count]->push(f_))
  77. return;
  78. }
  79. _queues[i % _count]->push(std::move(f_));
  80. }
  81. template<typename F>
  82. [[nodiscard]]
  83. std::future<typename std::result_of<F()>::type>
  84. enqueue_task(F&& f_)
  85. {
  86. using TaskReturnType = typename std::result_of<F()>::type;
  87. using Promise = std::promise<TaskReturnType>;
  88. auto i = _index.fetch_add(1,std::memory_order_relaxed);
  89. auto promise = std::make_shared<Promise>();
  90. auto future = promise->get_future();
  91. auto work = [=]()
  92. {
  93. auto rv = f_();
  94. promise->set_value(rv);
  95. };
  96. for(std::size_t n = 0; n < (_count * K); ++n)
  97. {
  98. if(_queues[(i + n) % _count]->push(work))
  99. return future;
  100. }
  101. _queues[i % _count]->push(std::move(work));
  102. return future;
  103. }
  104. public:
  105. std::vector<pthread_t>
  106. threads()
  107. {
  108. std::vector<pthread_t> rv;
  109. for(auto &thread : _threads)
  110. rv.push_back(thread.native_handle());
  111. return rv;
  112. }
  113. private:
  114. Queues _queues;
  115. private:
  116. std::vector<std::thread> _threads;
  117. private:
  118. const std::size_t _count;
  119. std::atomic_uint _index;
  120. static const unsigned int K = 2;
  121. };