cartographer 線程池+隊列

1.線程池

1.1 線程池定義

  • 簡單來說就是有一堆已經創建好的線程(最大數目一定),初始化時他們都處於空閒狀態,當有新的任務進來時,從線程池中取出一個空閒的線程處理任務,然後當任務處理完成之後,該線程被重新放回到線程池中,供其他的任務使用,當線程池中的線程都在處理任務時,就沒有空閒線程供使用,此時,若有新的任務產生,只能等待線程池中有線程結束任務空閒才能執行。
  • 線程池: 當進行並行的任務作業操作時,線程的建立與銷燬的開銷是,阻礙性能進步的關鍵,因此線程池,由此產生。使用多個線程,無限制循環等待隊列,進行計算和操作。幫助快速降低和減少性能損耗。

1.2 爲何使用線程池

優點:

  • 性能:每開啓一個新的線程都要消耗內存空間及資源(默認情況下大約1 MB的內存),同時多線程情況下操作系統必須調度可運行的線程並執行上下文切換,所以太多的線程還對性能不利。而線程池其目的是爲了減少開啓新線程消耗的資源(使用線程池中的空閒線程,不必再開啓新線程,以及統一管理線程(線程池中的線程執行完畢後,迴歸到線程池內,等待新任務))。

  • 時間:無論何時啓動一個線程,都需要時間(幾百毫秒),用於創建新的局部變量堆,線程池預先創建了一組可回收線程,因此可以縮短過載時間。

缺點:
線程池缺點:線程池的性能損耗優於線程(通過共享和回收線程的方式實現),但是:

  1. 線程池不支持線程的取消、完成、失敗、通知等交互性操作。
  2. 線程池不支持線程執行的先後次序排序。
  3. 不能設置池化線程(線程池內的線程)的Name,會增加代碼調試難度。
  4. 線程池通常都是後臺線程,優先級爲ThreadPriority.Normal。
  5. 線程池阻塞會影響性能(阻塞會使CLR錯誤地認爲它佔用了大量CPU,CLR能夠檢測或補償(往池中注入更多線程),但是這可能使線程池收到後續超負荷的影響。Task解決了這個問題)。
  6. 線程池使用的是全局隊列,全局隊列中的線程依舊會存在競爭共享資源的情況,從而影響性能(Task使用本地隊列解決了這個問題)

1.3 線程池的組成

線程池管理器:

  • 任務: 創先一定數量的線程,啓動線程,調配認爲,管理着線程池。
  • 本篇線程池目前只需要啓動(start()),停止方法(stop()),及任務添加方法(addTask).
    start()創建一定數量的線程池,進行線程循環.
    stop()停止所有線程循環,回收所有資源.
    addTask()添加任務.

工作線程:

  • 線程池中線程,在線程池中等待並執行分配的任務。
  • 本篇選用條件變量實現等待和通知機制。

任務接口:

  • 添加任務的接口,以供工作線程調度任務的執行。

任務隊列:

  • 用於存放沒有處理的任務。提供一種緩衝機制
    同時任務隊列具有調度功能,高優先級的任務放在任務隊列前面;本篇選用priority_queue 與pair的結合用作任務優先隊列的結構.

1.4 線程池工作的四種情況

假設我們的線程池大小爲3,任務隊列目前不做大小限制.

  • 1、主程序當前沒有任務要執行,線程池中的任務隊列爲空閒狀態.
    此情況下所有工作線程處於空閒的等待狀態,任務緩衝隊列爲空.
  • 2、主程序添加小於等於線程池中線程數量的任務.
    此情況基於情形1,所有工作線程已處在等待狀態,主線程開始添加三個任務,添加後通知(notif())喚醒線程池中的線程開始取(take())任務執行. 此時的任務緩衝隊列還是空。
  • 3、主程序添加任務數量大於當前線程池中線程數量的任務.
    此情況發生情形2後面,所有工作線程都在工作中,主線程開始添加第四個任務,添加後發現現在線程池中的線程用完了,於是存入任務緩衝隊列。工作線程空閒後主動從任務隊列取任務執行.
  • 4、主程序添加任務數量大於當前線程池中線程數量的任務,且任務緩衝隊列已滿.
    此情況發生情形3且設置了任務緩衝隊列大小後面,主程序添加第N個任務,添加後發現池子中的線程用完了,任務緩衝隊列也滿了,於是進入等待狀態、等待任務緩衝隊列中的任務騰空通知。

2.carto中線程池+隊列

2.1 任務隊列

2.1.1 頭文件

#ifndef CARTOGRAPHER_COMMON_TASK_H_
#define CARTOGRAPHER_COMMON_TASK_H_

#include <set>
#include <mutex>
#include "glog/logging.h"
#include "thread_pool.h"

namespace cartographer {
namespace common {

class ThreadPoolInterface;

class Task {
 public:
  friend class ThreadPoolInterface;
  //工作項目回調
  using WorkItem = std::function<void()>;
  //新增,已分派,依賴已完成,正在運行,已完成
  enum State { NEW, DISPATCHED, DEPENDENCIES_COMPLETED, RUNNING, COMPLETED };

  Task() = default;
  ~Task();

  //得到該 任務 的狀態
  State GetState();

  // State must be 'NEW'.
  void SetWorkItem(const WorkItem& work_item);

  // State must be 'NEW'. 'dependency' may be nullptr, in which case it is
  // assumed completed. 依賴可以爲空,但任務必須是 new
  void AddDependency(std::weak_ptr<Task> dependency);

 private:
  // Allowed in all states. 在所有狀態下都允許
  void AddDependentTask(Task* dependent_task);

  // State must be 'DEPENDENCIES_COMPLETED' and becomes 'COMPLETED'.
  void Execute();

  // State must be 'NEW' and becomes 'DISPATCHED' or 'DEPENDENCIES_COMPLETED'.
  void SetThreadPool(ThreadPoolInterface* thread_pool) ;

  // State must be 'NEW' or 'DISPATCHED'. If 'DISPATCHED', may become
  // 'DEPENDENCIES_COMPLETED'.
  void OnDependenyCompleted();

  WorkItem work_item_ ;
  ThreadPoolInterface* thread_pool_to_notify_ = nullptr;
  State state_  = NEW;
  unsigned int uncompleted_dependencies_  = 0;
  std::set<Task*> dependent_tasks_ ;

  std::mutex mutex_;
};

}  // namespace common
}  // namespace cartographer

#endif  // CARTOGRAPHER_COMMON_TASK_H_

2.1.2 .cc

/*
 * Copyright 2018 The Cartographer Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "task.h"

namespace cartographer {
namespace common {

Task::~Task() {
  // TODO(gaschler): Relax some checks after testing.
  if (state_ != NEW && state_ != COMPLETED) {
    LOG(WARNING) << "Delete Task between dispatch and completion.";
  }
}

Task::State Task::GetState() {
  std::unique_lock<std::mutex> locker(mutex_);
  return state_;
}

void Task::SetWorkItem(const WorkItem& work_item) {
  std::unique_lock<std::mutex> lock(mutex_);
  CHECK_EQ(state_, NEW);
  work_item_ = work_item;
}

void Task::AddDependency(std::weak_ptr<Task> dependency) {
  std::shared_ptr<Task> shared_dependency;
  {
    std::unique_lock<std::mutex> lock(mutex_);
    CHECK_EQ(state_, NEW);
    if ((shared_dependency = dependency.lock())) {
      ++uncompleted_dependencies_;
    }
  }
  if (shared_dependency) {
    shared_dependency->AddDependentTask(this);
  }
}

void Task::SetThreadPool(ThreadPoolInterface* thread_pool) {
  std::unique_lock<std::mutex> lock(mutex_);
  CHECK_EQ(state_, NEW);
  state_ = DISPATCHED;
  thread_pool_to_notify_ = thread_pool;
  if (uncompleted_dependencies_ == 0) {
    state_ = DEPENDENCIES_COMPLETED;
    CHECK(thread_pool_to_notify_);
    thread_pool_to_notify_->NotifyDependenciesCompleted(this);
  }
}

void Task::AddDependentTask(Task* dependent_task) {
  std::unique_lock<std::mutex> lock(mutex_);
  if (state_ == COMPLETED) {
    dependent_task->OnDependenyCompleted();
    return;
  }
  bool inserted = dependent_tasks_.insert(dependent_task).second;
  CHECK(inserted) << "Given dependency is already a dependency.";
}

void Task::OnDependenyCompleted() {
  std::unique_lock<std::mutex> lock(mutex_);
  CHECK(state_ == NEW || state_ == DISPATCHED);
  --uncompleted_dependencies_;
  if (uncompleted_dependencies_ == 0 && state_ == DISPATCHED) {
    state_ = DEPENDENCIES_COMPLETED;
    CHECK(thread_pool_to_notify_);
    thread_pool_to_notify_->NotifyDependenciesCompleted(this);
  }
}

void Task::Execute() {
  {
    std::unique_lock<std::mutex> lock(mutex_);
    CHECK_EQ(state_, DEPENDENCIES_COMPLETED);
    state_ = RUNNING;
  }

  // Execute the work item.
  if (work_item_) {
    work_item_();
  }

  std::unique_lock<std::mutex> lock(mutex_);
  state_ = COMPLETED;
  for (Task* dependent_task : dependent_tasks_) {
    dependent_task->OnDependenyCompleted();
  }
}

}  // namespace common
}  // namespace cartographer

2.2 線程池

2.2.1 頭文件

#ifndef CARTOGRAPHER_COMMON_THREAD_POOL_H_
#define CARTOGRAPHER_COMMON_THREAD_POOL_H_

#include <deque>
#include <functional>
#include <memory>
#include <thread>
#include <vector>
#include <map>
#include <mutex>
#include <condition_variable>
#include "task.h"

namespace cartographer {
namespace common {

class Task;

class ThreadPoolInterface {
 public:
  ThreadPoolInterface() {}
  virtual ~ThreadPoolInterface() {}
  virtual std::weak_ptr<Task> Schedule(std::unique_ptr<Task> task) = 0; //進程表

 protected:
  void Execute(Task* task);
  void SetThreadPool(Task* task);

 private:
  friend class Task;

  virtual void NotifyDependenciesCompleted(Task* task) = 0;
};


class ThreadPool : public ThreadPoolInterface {
 public:
  explicit ThreadPool(int num_threads);
  ~ThreadPool();

  ThreadPool(const ThreadPool&) = delete;
  ThreadPool& operator=(const ThreadPool&) = delete;
  // When the returned weak pointer is expired, 'task' has certainly completed,
  // so dependants no longer need to add it as a dependency.
  std::weak_ptr<Task> Schedule(std::unique_ptr<Task> task) override;

 private:
  void DoWork();

  void NotifyDependenciesCompleted(Task* task) override;

  std::mutex mutex_;      //同步
  std::condition_variable cv_task;
  bool running_  = true;
  std::vector<std::thread> pool_ ;  // 線程池
  std::deque<std::shared_ptr<Task>> task_queue_ ;   //任務隊列
  std::map <Task*, std::shared_ptr<Task>> tasks_not_ready_;   //
};

}  // namespace common
}  // namespace cartographer

#endif  // CARTOGRAPHER_COMMON_THREAD_POOL_H_

2.2.2 .cc

/*
 * Copyright 2016 The Cartographer Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "thread_pool.h"

#ifndef WIN32
#include <unistd.h>
#endif
#include <algorithm>
#include <chrono>
#include <numeric>

#include "task.h"
#include "glog/logging.h"

namespace cartographer {
namespace common {


void ThreadPoolInterface::Execute(Task* task) { task->Execute(); }

void ThreadPoolInterface::SetThreadPool(Task* task) {
  task->SetThreadPool(this);
}

//構造 線程池 參數是多少個線程
ThreadPool::ThreadPool(int num_threads) {
    //線程池個數小於1時,設定一個線程
    int threadNum = num_threads <1 ?1:num_threads;
    std::unique_lock<std::mutex> lock(mutex_);
  for (int i = 0; i != threadNum; ++i) {
      //構造每個線程池中的線程
    pool_.emplace_back([this]() { ThreadPool::DoWork(); });
  }
}

ThreadPool::~ThreadPool() {
  {
    std::unique_lock<std::mutex> lock(mutex_);
    CHECK(running_);
    running_ = false;
  }
   cv_task.notify_all();       // 喚醒所有線程執行
  for (std::thread& thread : pool_) {
    thread.join();
  }
}

void ThreadPool::NotifyDependenciesCompleted(Task* task) {
  std::unique_lock<std::mutex> lock(mutex_);
  auto it = tasks_not_ready_.find(task);
  CHECK(it != tasks_not_ready_.end());
  task_queue_.push_back(it->second);
  tasks_not_ready_.erase(it);
}

std::weak_ptr<Task> ThreadPool::Schedule(std::unique_ptr<Task> task) {
  std::shared_ptr<Task> shared_task;
  {
    std::unique_lock<std::mutex> lock(mutex_);
    auto insert_result =
        tasks_not_ready_.insert(std::make_pair(task.get(), std::move(task)));
    CHECK(insert_result.second) << "Schedule called twice";
    shared_task = insert_result.first->second;
  }
  SetThreadPool(shared_task.get());
  return shared_task;
}

void ThreadPool::DoWork() {
#ifdef __linux__
  // This changes the per-thread nice level of the current thread on Linux. We
  // do this so that the background work done by the thread pool is not taking
  // away CPU resources from more important foreground threads.
  CHECK_NE(nice(10), -1);
#endif
  //一個while 循環
  for (;;) {
      //一個共享任務指針
    std::shared_ptr<Task> task;
    {
        //數據互斥鎖
      std::unique_lock<std::mutex> lock(mutex_);
        //如果 任務隊列是空 或者 線程正在運行時
      cv_task.wait(lock, [this](){return !task_queue_.empty() || !running_;});
        //當任務不爲空時,將該任務賦給上面任務指針,並彈出
      if (!task_queue_.empty()) {
        task = std::move(task_queue_.front());
        task_queue_.pop_front();
      } else if (!running_) {
        return;
      }
    }
    CHECK(task);
    CHECK_EQ(task->GetState(), common::Task::DEPENDENCIES_COMPLETED);
    //執行任務
    Execute(task.get());
  }
}

}  // namespace common
}  // namespace cartographer

2.3 測試

namespace CartoCom = cartographer::common;

int test_int = 0;

void Test(int& index,const std::string& test){
    const int add_index = 5;
    int print_index = add_index+index;
    LOG(ERROR)<<"print: "<<test;
    while(index<print_index){
        index +=1;
        sleep(1);
        LOG(ERROR)<<"index=: "<<index;
    }
}

    LOG(ERROR)<<"start pointcloud_deal_node!";
    CartoCom::ThreadPool thread (3);
    std::unique_ptr<CartoCom::Task> test_task =  CartoCom::make_unique<CartoCom::Task>();
    test_task->SetWorkItem([=](){Test(test_int,"111111111");});
    std::weak_ptr<CartoCom::Task> return_ptr = thread.Schedule(std::move(test_task));

    std::unique_ptr<CartoCom::Task> test_task1 =  CartoCom::make_unique<CartoCom::Task>();
    test_task1->SetWorkItem([=](){Test(test_int,"22222222");});
    test_task1->AddDependency(return_ptr);
    std::weak_ptr<CartoCom::Task> return_ptr1 = thread.Schedule(std::move(test_task1));

    std::unique_ptr<CartoCom::Task> test_task2 =  CartoCom::make_unique<CartoCom::Task>();
    test_task2->SetWorkItem([=](){Test(test_int,"33333333");});
    test_task2->AddDependency(return_ptr1);
    std::weak_ptr<CartoCom::Task> return_ptr2 = thread.Schedule(std::move(test_task2));

    LOG(ERROR)<<"end pointcloud_deal_node!";
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章