12
返回列表 发新帖
楼主: John0773
跳转到指定楼层
上一主题 下一主题
收起左侧

[其他] multi threading系统设计

🔗
 楼主| John0773 2019-9-30 11:40:27 | 只看该作者
全局:
a5066987 发表于 2019-9-30 11:01
其实也是可以等的...这样就是一个有界队列了

我还是学得太浅... 不过层主说的好像都是Java相关的
回复

使用道具 举报

🔗
a5066987 2019-9-30 11:42:54 | 只看该作者
全局:
John0773 发表于 2019-9-30 11:40
我还是学得太浅... 不过层主说的好像都是Java相关的

这个东西跟语言没有关系吧,其实我是c++选手🤣
回复

使用道具 举报

🔗
magicsets 2019-9-30 13:23:00 | 只看该作者
全局:
这个问题其实可以有很多细节,不过主要是要有一个BlockingQueue(也叫ThreadSafeQueue)的抽象,写了一份参考代码..

  1. #include <atomic>
  2. #include <chrono>
  3. #include <cstddef>
  4. #include <iostream>
  5. #include <memory>
  6. #include <mutex>
  7. #include <optional>
  8. #include <queue>
  9. #include <set>
  10. #include <string>
  11. #include <thread>
  12. #include <utility>
  13. #include <vector>

  14. // 支持多个Producer/Consumder的简单阻塞队列
  15. template <typename T>
  16. class BlockingQueue {
  17. public:
  18.   explicit BlockingQueue(size_t capacity) : capacity_(capacity) {}

  19.   std::size_t Size() const {
  20.     std::lock_guard<std::mutex> lock(queue_mutex_);
  21.     return internal_queue_.size();
  22.   }

  23.   // 向队列中添加一个元素,如果当前队列大小等于’capacity_‘的话则阻塞等待
  24.   // 成功添加则返回true,持续等待'timeout_ms'毫秒后仍无法添加则返回false
  25.   template <typename U>
  26.   bool PushWithTimeout(U &&element, std::int64_t timeout_ms) {
  27.     std::unique_lock<std::mutex> lock(queue_mutex_);
  28.     if (internal_queue_.size() >= capacity_) {
  29.       queue_nonfull_condition_.wait_for(
  30.           lock, std::chrono::milliseconds(timeout_ms),
  31.           [&]{ return internal_queue_.size() < capacity_; });
  32.       if (internal_queue_.size() >= capacity_) {
  33.         return false;
  34.       }
  35.     }
  36.     internal_queue_.emplace(std::forward<U>(element));
  37.     lock.unlock();
  38.     queue_nonempty_condition_.notify_one();
  39.     return true;
  40.   }

  41.   // 从队列中取出一个元素,如果当前队列为空的话则阻塞等待
  42.   // 持续等待'timeout_ms'毫秒后则返回std::nullopt
  43.   std::optional<T> PopWithTimeOut(std::int64_t timeout_ms) {
  44.     std::unique_lock<std::mutex> lock(queue_mutex_);
  45.     if (internal_queue_.empty()) {
  46.       queue_nonempty_condition_.wait_for(
  47.           lock, std::chrono::milliseconds(timeout_ms),
  48.           [&]{ return !internal_queue_.empty(); });
  49.       if (internal_queue_.empty()) {
  50.         return std::nullopt;
  51.       }
  52.     }
  53.     T value(std::move(internal_queue_.front()));
  54.     internal_queue_.pop();
  55.     lock.unlock();
  56.     queue_nonfull_condition_.notify_one();
  57.     return value;
  58.   }

  59. private:
  60.   const size_t capacity_;
  61.   std::queue<T> internal_queue_;
  62.   mutable std::mutex queue_mutex_;
  63.   std::condition_variable queue_nonempty_condition_;
  64.   std::condition_variable queue_nonfull_condition_;
  65. };

  66. using ValueType = std::string;

  67. // 默认等待100ms为超时状态
  68. constexpr std::int64_t kDefaultTimeout = 100;

  69. // 如果每生成一个数据就发送给阻塞队列的话,会有严重的lock contention,性能还不如单线程
  70. // 所以实际中一般是一个batch一个batch的发,下面这个参数可以控制batch大小,比如可以设置成1000
  71. // 这里为了方便阅读程序的输出只设置成1
  72. constexpr size_t kMaxBatchSize = 1;

  73. using DataBatch = std::unique_ptr<std::vector<ValueType>>;

  74. // 生成数据的线程
  75. class StreamingWorker {
  76. public:
  77.   // 每秒生成'messages_per_second'条数据
  78.   StreamingWorker(size_t worker_id, std::int64_t messages_per_second,
  79.                   BlockingQueue<DataBatch>* blocking_queue)
  80.       : worker_id_(worker_id),
  81.         interval_ms_(1000 / messages_per_second),
  82.         blocking_queue_(blocking_queue),
  83.         running_(false) {}

  84.   // 创建线程并运行
  85.   void Start() {
  86.     running_.store(true);
  87.     thread_ = std::make_unique<std::thread>([this] {
  88.       this->Run();
  89.     });
  90.   }

  91.   // 结束线程
  92.   void Stop() {
  93.     running_.store(false);
  94.     thread_->join();
  95.     thread_ = nullptr;
  96.   }

  97. private:
  98.   // 不断生成数据并批量添加到共享数据队列中
  99.   void Run() {
  100.     DataBatch batch = NewBatch();
  101.     while (running_) {
  102.       if (batch->size() >= kMaxBatchSize) {
  103.         // 这里使用了timeout机制避免线程block后无法正常终止
  104.         if (!blocking_queue_->PushWithTimeout(std::move(batch),
  105.                                               kDefaultTimeout)) {
  106.           continue;
  107.         }
  108.         batch = NewBatch();
  109.       }
  110.       ++message_id_;
  111.       const std::string message =
  112.           static_cast<char>('A' + worker_id_) + std::to_string(message_id_);
  113.       // 注:这里用字符串拼接而不是输出流是为了避免多线程情况下的输出串流
  114.       std::cout << ("Streaming worker " + std::to_string(worker_id_) + " is "
  115.                     "generating message " + message + "\n");
  116.       batch->push_back("message " + message + " from worker " +
  117.                        std::to_string(worker_id_));
  118.       std::this_thread::sleep_for(std::chrono::milliseconds(interval_ms_));
  119.     }
  120.     std::cout << ("Streaming worker " + std::to_string(worker_id_) +
  121.                   " is stopping\n");
  122.   }

  123.   static DataBatch NewBatch() {
  124.     DataBatch batch = std::make_unique<std::vector<ValueType>>();
  125.     batch->reserve(kMaxBatchSize);
  126.     return batch;
  127.   }

  128. private:
  129.   const size_t worker_id_;
  130.   const std::int64_t interval_ms_;
  131.   BlockingQueue<DataBatch>* const blocking_queue_;
  132.   std::unique_ptr<std::thread> thread_;
  133.   std::atomic<bool> running_;
  134.   size_t message_id_ = 0;
  135. };

  136. // 很多情况下,从数据源获得的数据需要根据一定方式分割(比如hash值,key range等等)后
  137. // 再写入到不同的文件,每个文件称作一个shard。
  138. //
  139. // 这里为了简单就不做sharding(也叫做partitioning、shuffling)操作了,
  140. // 也不写入文件而是直接打印。
  141. class MaterializationWorker {
  142. public:
  143.   // 每秒打印'batches_per_second'个batch的数据
  144.   MaterializationWorker(size_t worker_id, std::int64_t batches_per_second,
  145.                         BlockingQueue<DataBatch>* blocking_queue)
  146.       : worker_id_(worker_id),
  147.         interval_ms_(1000 / batches_per_second),
  148.         blocking_queue_(blocking_queue),
  149.         state_(INIT) {}

  150.   // 创建线程并运行
  151.   void Start() {
  152.     state_.store(RUNNING);
  153.     thread_ = std::make_unique<std::thread>([this] {
  154.       this->Run();
  155.     });
  156.   }

  157.   // 结束线程
  158.   void Stop() {
  159.     state_.store(TERMINATED);
  160.     thread_->join();
  161.     thread_ = nullptr;
  162.   }

  163.   // 处理完数据队列中的所有数据。然后结束线程
  164.   void DrainAndStop() {
  165.     state_.store(DRAINING);
  166.     thread_->join();
  167.     thread_ = nullptr;
  168.   }

  169. private:
  170.   void Run() {
  171.     while (true) {
  172.       if (state_ == TERMINATED) break;
  173.       if (state_ == DRAINING && blocking_queue_->Size() == 0) break;
  174.       const std::optional<DataBatch> batch =
  175.           blocking_queue_->PopWithTimeOut(kDefaultTimeout);
  176.       if (!batch.has_value()) {
  177.         continue;
  178.       }
  179.       for (const ValueType& value : **batch) {
  180.         std::cout << ("Materialization worker " + std::to_string(worker_id_) +
  181.                       " is writing " + value + " to file\n");
  182.       }
  183.       std::this_thread::sleep_for(std::chrono::milliseconds(interval_ms_));
  184.     }
  185.     std::cout << ("Materialization worker " + std::to_string(worker_id_) +
  186.                   " is stopping\n");
  187.   }

  188.   enum State {
  189.     INIT = 0,
  190.     RUNNING = 1,
  191.     DRAINING = 2,
  192.     TERMINATED = 3
  193.   };


  194.   const size_t worker_id_;
  195.   const std::int64_t interval_ms_;
  196.   BlockingQueue<DataBatch>* const blocking_queue_;
  197.   std::unique_ptr<std::thread> thread_;
  198.   std::atomic<State> state_;
  199. };

  200. int main(int argc, char* argv[]) {
  201.   // 生成数据的线程数量
  202.   const size_t kNumStreamingWorkers = 3;

  203.   // 每秒每线程数据生成速度
  204.   const std::int64_t kStreamingRate = 2;

  205.   // 写入文件的线程数量
  206.   const size_t kNumMaterializationWorkers = 2;

  207.   // 每秒每线程数据写入速度
  208.   const std::int64_t kBatchMaterializationRate = 1;

  209.   // 共享队列大小
  210.   const std::size_t kBlockingQueueCapacity = 4;


  211.   BlockingQueue<DataBatch> blocking_queue(kBlockingQueueCapacity);

  212.   std::int64_t worker_id = 0;

  213.   std::vector<std::unique_ptr<StreamingWorker>> streaming_workers;
  214.   std::vector<std::unique_ptr<MaterializationWorker>> materialization_workers;

  215.   // 创建并运行数据生成线程
  216.   for (size_t i = 0; i < kNumStreamingWorkers; ++i) {
  217.     streaming_workers.emplace_back(
  218.         std::make_unique<StreamingWorker>(
  219.             worker_id++, kStreamingRate, &blocking_queue));
  220.     streaming_workers.back()->Start();
  221.   }

  222.   // 创建并运行写文件线程
  223.   for (size_t i = 0; i < kNumMaterializationWorkers; ++i) {
  224.     materialization_workers.emplace_back(
  225.         std::make_unique<MaterializationWorker>(
  226.             worker_id++, kBatchMaterializationRate, &blocking_queue));
  227.     materialization_workers.back()->Start();
  228.   }

  229.   // 等待10秒
  230.   std::this_thread::sleep_for(std::chrono::seconds(10));

  231.   // 终止所有数据生成线程
  232.   for (auto& worker : streaming_workers) {
  233.     worker->Stop();
  234.   }

  235.   // 终止所有写文件线程
  236.   for (auto& worker : materialization_workers) {
  237.     worker->DrainAndStop();
  238.   }

  239.   return 0;
  240. }
复制代码

评分

参与人数 3大米 +5 收起 理由
John0773 + 1 给你点个赞!
jaychsu + 3 纯粹看到前一个加分的姿势清奇。。。这 666.
a5066987 + 1 0110 0110 0110

查看全部评分

回复

使用道具 举报

🔗
a6219221 2019-9-30 14:02:30 | 只看该作者
全局:
用golang非常简单。用producer和consumer的方式。
这是一个example:
https://gobyexample.com/worker-pools

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册账号
隐私提醒:
  • ☑ 禁止发布广告,拉群,贴个人联系方式:找人请去🔗同学同事飞友,拉群请去🔗拉群结伴,广告请去🔗跳蚤市场,和 🔗租房广告|找室友
  • ☑ 论坛内容在发帖 30 分钟内可以编辑,过后则不能删帖。为防止被骚扰甚至人肉,不要公开留微信等联系方式,如有需求请以论坛私信方式发送。
  • ☑ 干货版块可免费使用 🔗超级匿名:面经(美国面经、中国面经、数科面经、PM面经),抖包袱(美国、中国)和录取汇报、定位选校版
  • ☑ 查阅全站 🔗各种匿名方法

本版积分规则

>
快速回复 返回顶部 返回列表