data_reader、internalthread以及blocking_queue的实现细节

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xizero00/article/details/50901204

(1)data_reader.cpp

首先介绍一下boost::weak_ptr;
弱引用是为了解决shared_ptr在循环引用下的内存释放问题而产生的。
弱引用当引用的对象活着的时候不一定存在。仅仅是当它存在的时候的一个引用。弱引用并不修改该对象的引用计数,这意味这弱引用它并不对对象的内存进行管理,在功能上类似于普通指针,然而一个比较大的区别是, 弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。
由于弱引用不更改引用计数,类似普通指针,只要把循环引用的一方使用弱引用,即可解除循环引用。

1)DataReader类中的变量:


   
   
  1. shared_ptr<Body> body_;
  2. static map< const string, boost::weak_ptr<DataReader::Body> > bodies_;
  3. const shared_ptr<QueuePair> queue_pair_;


2)此外还有构造函数:


   
   
  1. explicit DataReader(const LayerParameter& param);
  2. 内联函数:
  3. inline BlockingQueue<Datum*>& free() const {
  4. return queue_pair_->free_;
  5. }
  6. inline BlockingQueue<Datum*>& full() const {
  7. return queue_pair_->full_;
  8. }



3)除此之外:

内部还定义了一个Body类,该类是继承于InternalThread
内部还定义了一个QueuePair类,该类有free和full函数,该类用于在body和readers之间进行数据分享


(2)此外该类还涉及到另一个类BlockingQueue,该类位于/util/block_queue.hpp里


1)BlockingQueue类有成员函数


   
   
  1. void push(const T& t);
  2. bool try_pop(T* t);
  3. T pop(const string& log_on_wait = "");
  4. bool try_peek(T* t);
  5. T peek();



2)此外该类内部还有一个sync的类(该类内部有同步机制和互斥机制)

该类的定义如下:

   
   
  1. template< typename T>
  2. class BlockingQueue<T>::sync {
  3. public:
  4. mutable boost::mutex mutex_;
  5. boost::condition_variable condition_;
  6. };


该类内部包含一个mutex_互斥量
还有一个条件变量condition_


3)局部的变量有:


   
   
  1. std::queue<T> queue_;
  2. shared_ptr<sync> sync_;




BlockingQueue的push函数的实现如下:

   
   
  1. void BlockingQueue<T>::push( const T& t) {
  2. boost::mutex:: scoped_lock lock(sync_->mutex_); //关于锁后面会详细讲
  3. queue_.push(t);
  4. lock.unlock();
  5. sync_->condition_.notify_one();
  6. }


首先尝试锁住,然后将数据push到队列(queue_ 是std::queue<T> 类型的),然后unlock,条件变量通知。


BlockingQueue的try_pop函数的实现如下:

   
   
  1. template< typename T>
  2. bool BlockingQueue<T>::try_pop(T* t) {
  3. boost::mutex:: scoped_lock lock(sync_->mutex_); //
  4. if (queue_.empty()) {
  5. return false;
  6. }
  7. *t = queue_.front();
  8. queue_.pop();
  9. return true;
  10. }



这里插播一段关于互斥锁的知识:

上述的代码中:
typedef unique_lock<mutex> scoped_lock;
scoped_lock是unique_lock<mutex>类型,因此通过查看boost的文档知道:
std::unique_lock<std::mutex> is the tool of choice when your locking needs are more complex than a simple lock at the beginning followed unconditionally by an unlock at the end.
也就是说当你的锁需求比简单的情况:一般的应用都是以lock开始,然后最后再unlock这样的情况,但是更复杂的时候你就需要scoped_lock。
参考文档:
为了解释这种锁的必要性,考虑下面的例子:

   
   
  1. class A
  2. {
  3. mutable std::mutex mut_;
  4. std:: vector< double> data_;
  5. public:
  6. // ...
  7. A& operator=( const A& rhs)
  8. {
  9. if ( this != &rhs)
  10. {
  11. std::unique_lock< std::mutex> lhs_lock(mut_);
  12. std::unique_lock< std::mutex> rhs_lock(rhs.mut_); // 死锁
  13. // assign data ...
  14. data_ = rhs.data_;
  15. }
  16. return * this;
  17. }
  18. // ...
  19. };


如果线程1:
A a1();
另一个线程2复制:
A a2=a1;
而原先的线程1此时再赋值:
a1=a2; 
这个时候就死锁了。。。碰到这个问题真是无解。。。
不过幸好我们还有解决方法,可以将上述代码写成:

    
    
  1. class A
  2. {
  3. mutable std::mutex mut_;
  4. std:: vector< double> data_;
  5. public:
  6. // ...
  7. A& operator=( const A& rhs)
  8. {
  9. if ( this != &rhs)
  10. {
  11. std::unique_lock< std::mutex> lhs_lock( mut_, std::defer_lock); // 其定义为:struct defer_lock_t {};一个空的标记类而已 通常作为参数传入给 unique_lock 或 lock_guard 的构造函数
  12. std::unique_lock< std::mutex> rhs_lock(rhs.mut_, std::defer_lock);
  13. std::lock(lhs_lock, rhs_lock);
  14. // assign data ...
  15. data_ = rhs.data_;
  16. }
  17. return * this;
  18. }
  19. // ...
  20. };



通过std::lock同时锁住两个,这样就能防止死锁了。
那么为什么新的代码能够避免这个问题:
a)首先lhs_lock和rhs_lock构建的时候是没有锁住的,因为unique_locks并没有引用他们(用了这个参数std::defer_lock )
b)std::lock(lhs_lock, rhs_lock);同时所住着两个mutex,而不会死锁,这是它的功能
c)这儿不能用lock_guard是因为lock并不拥有所引用的mutex的模式,如果尝试编译safe_guard的话那么就无法编译
总结:也就是说遇到这种循环引用的,要先构建两个不锁的mutex,然后同时上锁(将两个资源上锁)。错误的代码是先锁住其中一个,然后再锁另一个。。。

这里再插播关于条件变量的知识:


条件变量是提供了一种机制,该机制能够等待另一个线程发来的通知,如果另一个线程满足某个条件的话。通常使用条件变量是这样的,一个线程锁住mutex,然后wait,当该线程醒来的时候会检查条件变量的值是否true,如果是则放行,否则继续睡。。。
为了介绍条件变量,给出下面的例子:

     
     
  1. boost::condition_variable cond;
  2. boost::mutex mut; bool data_ready;
  3. void process_data();
  4. void wait_for_data_to_process(){
  5. boost::unique_lock<boost::mutex> lock(mut);
  6. while(!data_ready) // lock保护变量data_ready
  7. {
  8. cond.wait(lock);
  9. }
  10. process_data();
  11. }


上述代码的含义是:先定义一个lock,注意,此时是使用的unique_lock,并且mutex是关联上lock,也就是说此时是互斥的,假设处理数据的线程是多个的,然后用条件变量的wait,将线程陷入睡眠
此时另一个线程在准备数据

     
     
  1. void retrieve_data();
  2. void prepare_data();
  3. void prepare_data_for_processing(){
  4. retrieve_data();
  5. prepare_data();
  6. {
  7. boost::lock_guard<boost::mutex> lock(mut);
  8. data_ready= true; // lock保护变量data_ready
  9. }
  10. cond.notify_one();
  11. }


当多个准备数据线程坑次坑次把数据搞定后,发送通知,那么原来的线程就醒来开始干活。




接下来继续BlockingQueue的实现代码:



   
   
  1. BlockingQueue的pop函数的实现如下:
  2. template< typename T>
  3. T BlockingQueue<T>::pop( const string& log_on_wait) {
  4. boost::mutex:: scoped_lock lock(sync_->mutex_); // 锁住
  5. while (queue_.empty()) {
  6. if (!log_on_wait.empty()) {
  7. LOG_EVERY_N(INFO, 1000)<< log_on_wait;
  8. }
  9. sync_->condition_.wait(lock); // 如果队列一直为空则一直在等待
  10. }
  11. T t = queue_.front(); // 否则取出
  12. queue_.pop();
  13. return t;
  14. }



BlockingQueue的try_peek函数的实现如下:
该函数是判断队列首部是不是有数据

   
   
  1. template< typename T>
  2. bool BlockingQueue<T>::try_peek(T* t) {
  3. boost::mutex:: scoped_lock lock(sync_->mutex_);
  4. if (queue_.empty()) {
  5. return false;
  6. }
  7. *t = queue_.front();
  8. return true;
  9. }




BlockingQueue的peek 函数的实现如下:
该函数取出队列首部的数据,同样也是使用的条件变量来实现同步

   
   
  1. template< typename T>
  2. T BlockingQueue<T>::peek() {
  3. boost::mutex:: scoped_lock lock(sync_->mutex_);
  4. while (queue_.empty()) {
  5. sync_->condition_.wait(lock);
  6. }
  7. return queue_.front();
  8. }



BlockingQueue的size 函数的实现如下:

   
   
  1. template< typename T>
  2. size_t BlockingQueue<T>::size() const {
  3. boost::mutex:: scoped_lock lock(sync_->mutex_);
  4. return queue_.size();
  5. }




最后定义了几个类型的BlockingQueue类

   
   
  1. template class BlockingQueue<Batch<float>*>;
  2. template class BlockingQueue<Batch<double>*>;
  3. template class BlockingQueue<Datum*>;
  4. template class BlockingQueue<shared_ptr<DataReader::QueuePair> >;
  5. template class BlockingQueue<P2PSync<float>*>;
  6. template class BlockingQueue<P2PSync<double>*>;




讲完了BlockingQueue类接下来讲DataReader内部的QueuePair类的实现:


首先甩出定义:

   
   
  1. class QueuePair {
  2. public:
  3. explicit QueuePair(int size);
  4. ~QueuePair();
  5. BlockingQueue<Datum*> free_;
  6. BlockingQueue<Datum*> full_;
  7. DISABLE_COPY_AND_ASSIGN(QueuePair);
  8. };



从定义里面可以看出定义了两个阻塞队列free_和full_,刚才分析了阻塞队列之后,这次回头看就不懵逼了。


接着看看具体实现:

构造函数做了些啥呢?
就是根据给定的size初始化的若干个Datum(本文最后会给出该数据结构的定义)的实例到free里面。

   
   
  1. DataReader::QueuePair::QueuePair( int size) {
  2. // Initialize the free queue with requested number of datums
  3. for ( int i = 0; i < size; ++i) {
  4. free_.push( new Datum());
  5. }
  6. }




析构函数做了些啥呢?
就是将full_和free_这两个队列里面的Datum对象全部delete。

   
   
  1. DataReader::QueuePair::~QueuePair() {
  2. Datum* datum;
  3. while (free_.try_pop(&datum)) {
  4. delete datum;
  5. }
  6. while (full_.try_pop(&datum)) {
  7. delete datum;
  8. }
  9. }


接下来看看Body类的实现,该类是继承自InternalThread 这个类的


   
   
  1. class Body : public InternalThread {
  2. public:
  3. explicit Body(const LayerParameter& param);
  4. virtual ~Body();
  5. protected:
  6. void InternalThreadEntry();
  7. void read_one(db::Cursor* cursor, QueuePair* qp);
  8. const LayerParameter param_;
  9. BlockingQueue< shared_ptr<QueuePair> > new_queue_pairs_;
  10. friend class DataReader;
  11. DISABLE_COPY_AND_ASSIGN(Body);
  12. };


Body里面重写了InternalThread内部的InternalThreadEntry函数,此外还添加了read_one函数
Body内部有DataReader的友元,以及BlockingQueue<shared_ptr<QueuePair> > new_queue_pairs_;

为了弄清楚究竟干啥,有必要了解InternalThread这个类究竟干了哪些工作?



InternalThread类实际上就是boost库的thread的封装
首先看看该类的定义是啥:

   
   
  1. class InternalThread {
  2. public:
  3. // 构造函数和析构函数
  4. InternalThread() : thread_() {}
  5. virtual ~InternalThread();
  6. /**
  7. * Caffe's thread local state will be initialized using the current
  8. * thread values, e.g. device id, solver index etc. The random seed
  9. * is initialized using caffe_rng_rand.
  10. * caffe的线程局部状态将会使用当前线程值来进行初始化,当前的线程的值有设备id,solver的编号、随机数种子等
  11. */
  12. void StartInternalThread();
  13. /** Will not return until the internal thread has exited. */
  14. // 是否知道线程退出才返回
  15. void StopInternalThread();
  16. // 线程是否已经起来了
  17. bool is_started() const;
  18. protected:
  19. /* Implement this method in your subclass
  20. with the code you want your thread to run. */
  21. // 定义了一个虚函数,要求继承该类的必须要实现之
  22. virtual void InternalThreadEntry() {}
  23. /* Should be tested when running loops to exit when requested. */
  24. // 在当请求退出的时候应该调用该函数
  25. bool must_stop();
  26. private:
  27. void entry(int device, Caffe::Brew mode, int rand_seed, int solver_count,
  28. bool root_solver);
  29. // 内部的成员变量
  30. shared_ptr<boost::thread> thread_;
  31. };
  32. } // namespace caffe
  33. 好了,看完类的定义代码的注释之后。我们来看看具体的实现
  34. namespace caffe {
  35. // 析构函数,调用停止内部线程函数
  36. InternalThread::~InternalThread() {
  37. StopInternalThread();
  38. }
  39. // 测试线程是否起来
  40. bool InternalThread::is_started() const {
  41. return thread_ && thread_->joinable(); // 首先thread_指针不能为空,然后该线程是可等待的(joinable)
  42. }
  43. bool InternalThread::must_stop() {
  44. // if interruption has been requested for the current thread, false otherwise. 见boost的doc
  45. return thread_ && thread_->interruption_requested();
  46. }
  47. // 初始化工作,然后
  48. void InternalThread::StartInternalThread() {
  49. CHECK(!is_started()) << "Threads should persist and not be restarted.";
  50. int device = 0;
  51. #ifndef CPU_ONLY
  52. CUDA_CHECK(cudaGetDevice(&device));
  53. #endif
  54. Caffe::Brew mode = Caffe::mode();
  55. int rand_seed = caffe_rng_rand();
  56. int solver_count = Caffe::solver_count();
  57. bool root_solver = Caffe::root_solver();
  58. try { // 然后重新实例化一个thread对象给thread_指针,该线程的执行的是entry函数
  59. thread_.reset( new boost::thread(&InternalThread::entry, this, device, mode,
  60. rand_seed, solver_count, root_solver));
  61. } catch ( std::exception& e) {
  62. LOG(FATAL) << "Thread exception: " << e.what();
  63. }
  64. }
  65. // 线程所要执行的函数
  66. void InternalThread::entry( int device, Caffe::Brew mode, int rand_seed,
  67. int solver_count, bool root_solver) {
  68. #ifndef CPU_ONLY
  69. CUDA_CHECK(cudaSetDevice(device));
  70. #endif
  71. Caffe::set_mode(mode);
  72. Caffe::set_random_seed(rand_seed);
  73. Caffe::set_solver_count(solver_count);
  74. Caffe::set_root_solver(root_solver);
  75. InternalThreadEntry();
  76. }
  77. // 停止线程
  78. void InternalThread::StopInternalThread() {
  79. if (is_started()) { // 如果线程已经开始
  80. thread_->interrupt(); // 那么打断
  81. try {
  82. thread_->join(); // 等待线程结束
  83. } catch (boost::thread_interrupted&) { //如果被打断,啥也不干,因为是自己要打断的^_^
  84. } catch ( std::exception& e) { // 如果发生其他错误则记录到日志
  85. LOG(FATAL) << "Thread exception: " << e.what();
  86. }
  87. }
  88. }
  89. } // namespace caffe




总结一下:无非就是获取线程的状态、启动线程、以及定义的线程入口函数InternalThread::entry ,这个入口函数很有意思,里面调用了虚函数InternalThreadEntry,并且在调用之前,帮用户做好了初始化的工作(随机数种子,CUDA、工作模式及GPU还是CPU、solver的类型)。



好了插播了这么多,咱们回头继续看Body类的情况,


   
   
  1. class Body : public InternalThread {
  2. public:
  3. explicit Body(const LayerParameter& param);
  4. virtual ~Body();
  5. protected:
  6. void InternalThreadEntry();
  7. void read_one(db::Cursor* cursor, QueuePair* qp);
  8. const LayerParameter param_;
  9. BlockingQueue< shared_ptr<QueuePair> > new_queue_pairs_;
  10. friend class DataReader;
  11. DISABLE_COPY_AND_ASSIGN(Body);
  12. };




Body类里面果然重写了InternalThread的虚函数InternalThreadEntry。
我们来看看Body的情况


   
   
  1. //Body类的构造函数,实际上是给定网络的参数,然后开始启动内部线程
  2. DataReader::Body::Body( const LayerParameter& param)
  3. : param_(param),
  4. new_queue_pairs_() {
  5. StartInternalThread(); // 调用InternalThread内部的函数来初始化运行环境以及新建线程去执行虚函数InternalThreadEntry的内容
  6. }
  7. // 析构,停止线程
  8. DataReader::Body::~Body() {
  9. StopInternalThread();
  10. }
  11. // 自己实现的需要执行的函数
  12. // 首先打开数据库,然后设置游标,然后设置QueuePair指针容器
  13. void DataReader::Body::InternalThreadEntry() {
  14. // 获取所给定的数据源的类型来得到DB的指针
  15. shared_ptr<db::DB> db(db::GetDB(param_.data_param().backend()));
  16. // 从网络参数中给定的DB的位置打开DB
  17. db->Open(param_.data_param().source(), db::READ);
  18. // 新建游标指针
  19. shared_ptr<db::Cursor> cursor(db->NewCursor());
  20. // 新建QueuePair指针容器,QueuePair里面包含了free_和full_这两个阻塞队列
  21. vector< shared_ptr<QueuePair> > qps;
  22. try {
  23. // 根据网络参数的阶段来设置solver_count
  24. int solver_count = param_.phase() == TRAIN ? Caffe::solver_count() : 1;
  25. // To ensure deterministic runs, only start running once all solvers
  26. // are ready. But solvers need to peek on one item during initialization,
  27. // so read one item, then wait for the next solver.
  28. for ( int i = 0; i < solver_count; ++i) {
  29. shared_ptr<QueuePair> qp(new_queue_pairs_.pop());
  30. read_one(cursor.get(), qp.get()); // 读取一个数据
  31. qps.push_back(qp);压入
  32. }
  33. // Main loop
  34. while (!must_stop()) {
  35. for ( int i = 0; i < solver_count; ++i) {
  36. read_one(cursor.get(), qps[i].get());
  37. }
  38. // Check no additional readers have been created. This can happen if
  39. // more than one net is trained at a time per process, whether single
  40. // or multi solver. It might also happen if two data layers have same
  41. // name and same source.
  42. CHECK_EQ(new_queue_pairs_.size(), 0);
  43. }
  44. } catch (boost::thread_interrupted&) {
  45. // Interrupted exception is expected on shutdown
  46. }
  47. }



   
   
  1. // 从数据库中获取一个数据
  2. void DataReader::Body::read_one(db::Cursor* cursor, QueuePair* qp) {
  3. // 从QueuePair中的free_队列pop出一个
  4. Datum* datum = qp->free_.pop();
  5. // TODO deserialize in-place instead of copy?
  6. // 然后解析cursor中的值
  7. datum->ParseFromString(cursor->value());
  8. // 然后压入QueuePair中的full_队列
  9. qp->full_.push(datum);
  10. // go to the next iter
  11. // 游标指向下一个
  12. cursor->Next();
  13. if (!cursor->valid()) {
  14. DLOG(INFO) << "Restarting data prefetching from start.";
  15. cursor->SeekToFirst(); // 如果游标指向的位置已经无效了则指向第一个位置
  16. }
  17. }






OK接下来我们收拾DataReader类剩下的部分,这里我就偷个懒把DataReader类的所有代码的注释都贴上去。


   
   
  1. #include <boost/thread.hpp>
  2. #include <map>
  3. #include <string>
  4. #include <vector>
  5. #include "caffe/common.hpp"
  6. #include "caffe/data_reader.hpp"
  7. #include "caffe/layers/data_layer.hpp"
  8. #include "caffe/proto/caffe.pb.h"
  9. namespace caffe {
  10. // 用于解决share_ptr在循环引用的时候的内存释放
  11. using boost::weak_ptr;
  12. map< const string, weak_ptr<DataReader::Body> > DataReader::bodies_;
  13. static boost::mutex bodies_mutex_;
  14. // 构造函数,传入的是网络的参数、
  15. // 初始化queue_pair_(里面包含两个阻塞队列free_和full_)
  16. DataReader::DataReader( const LayerParameter& param)
  17. : queue_pair_( new QueuePair( //
  18. param.data_param().prefetch() * param.data_param().batch_size())) {
  19. // Get or create a body
  20. // 首先创建或者获取一个body实例
  21. boost::mutex:: scoped_lock lock(bodies_mutex_);
  22. string key = source_key(param); // 从网络参数中获取key
  23. weak_ptr<Body>& weak = bodies_[key]; // bodies_是存放的string到Body的映射
  24. body_ = weak.lock();
  25. if (!body_) { // 如果bodies是空的
  26. body_.reset( new Body(param)); // 则新建Body实例到body_
  27. bodies_[key] = weak_ptr<Body>(body_); // 然后存放到bodies_中去
  28. }
  29. body_->new_queue_pairs_.push(queue_pair_); // 并将queue_pair放入body_中的new_queue_pairs_中去
  30. }
  31. // 析构函数
  32. DataReader::~DataReader() {
  33. string key = source_key(body_->param_);
  34. body_.reset();
  35. boost::mutex:: scoped_lock lock(bodies_mutex_); // 上锁
  36. if (bodies_[key].expired()) {
  37. bodies_.erase(key); // map里面的erase
  38. }
  39. }
  40. //
  41. DataReader::QueuePair::QueuePair( int size) {
  42. // Initialize the free queue with requested number of datums
  43. // 一开始全部压入free
  44. for ( int i = 0; i < size; ++i) {
  45. free_.push( new Datum());
  46. }
  47. }
  48. // 删除free_和full_内的datum
  49. DataReader::QueuePair::~QueuePair() {
  50. Datum* datum;
  51. while (free_.try_pop(&datum)) {
  52. delete datum;
  53. }
  54. while (full_.try_pop(&datum)) {
  55. delete datum;
  56. }
  57. }
  58. //Body类的构造函数,实际上是给定网络的参数,然后开始启动内部线程
  59. DataReader::Body::Body( const LayerParameter& param)
  60. : param_(param),
  61. new_queue_pairs_() {
  62. StartInternalThread(); // 调用InternalThread内部的函数来初始化运行环境以及新建线程去执行虚函数InternalThreadEntry的内容
  63. }
  64. // 析构,停止线程
  65. DataReader::Body::~Body() {
  66. StopInternalThread();
  67. }
  68. // 自己实现的需要执行的函数
  69. // 首先打开数据库,然后设置游标,然后设置QueuePair指针容器
  70. void DataReader::Body::InternalThreadEntry() {
  71. // 获取所给定的数据源的类型来得到DB的指针
  72. shared_ptr<db::DB> db(db::GetDB(param_.data_param().backend()));
  73. // 从网络参数中给定的DB的位置打开DB
  74. db->Open(param_.data_param().source(), db::READ);
  75. // 新建游标指针
  76. shared_ptr<db::Cursor> cursor(db->NewCursor());
  77. // 新建QueuePair指针容器,QueuePair里面包含了free_和full_这两个阻塞队列
  78. vector< shared_ptr<QueuePair> > qps;
  79. try {
  80. // 根据网络参数的阶段来设置solver_count
  81. int solver_count = param_.phase() == TRAIN ? Caffe::solver_count() : 1;
  82. // To ensure deterministic runs, only start running once all solvers
  83. // are ready. But solvers need to peek on one item during initialization,
  84. // so read one item, then wait for the next solver.
  85. for ( int i = 0; i < solver_count; ++i) {
  86. shared_ptr<QueuePair> qp(new_queue_pairs_.pop());
  87. read_one(cursor.get(), qp.get()); // 读取一个数据
  88. qps.push_back(qp);压入
  89. }
  90. // Main loop
  91. while (!must_stop()) {
  92. for ( int i = 0; i < solver_count; ++i) {
  93. read_one(cursor.get(), qps[i].get());
  94. }
  95. // Check no additional readers have been created. This can happen if
  96. // more than one net is trained at a time per process, whether single
  97. // or multi solver. It might also happen if two data layers have same
  98. // name and same source.
  99. CHECK_EQ(new_queue_pairs_.size(), 0);
  100. }
  101. } catch (boost::thread_interrupted&) {
  102. // Interrupted exception is expected on shutdown
  103. }
  104. }
  105. // 从数据库中获取一个数据
  106. void DataReader::Body::read_one(db::Cursor* cursor, QueuePair* qp) {
  107. // 从QueuePair中的free_队列pop出一个
  108. Datum* datum = qp->free_.pop();
  109. // TODO deserialize in-place instead of copy?
  110. // 然后解析cursor中的值
  111. datum->ParseFromString(cursor->value());
  112. // 然后压入QueuePair中的full_队列
  113. qp->full_.push(datum);
  114. // go to the next iter
  115. // 游标指向下一个
  116. cursor->Next();
  117. if (!cursor->valid()) {
  118. DLOG(INFO) << "Restarting data prefetching from start.";
  119. cursor->SeekToFirst(); // 如果游标指向的位置已经无效了则指向第一个位置
  120. }
  121. }
  122. } // namespace caffe

总结:实际上该数据层就是调用了封装层的DB来读取数据,此外还简单封装了boost的线程库,然后自己封装了个阻塞队列。

最后还有Datum究竟是哈
可以看caffe.proto文件中的定义

message Datum {
  optional int32 channels = 1;
  optional int32 height = 2;
  optional int32 width = 3;
  // the actual image data, in bytes
  optional bytes data = 4;
  optional int32 label = 5;
  // Optionally, the datum could also hold float data.
  repeated float float_data = 6;
  // If true data contains an encoded image that need to be decoded
  optional bool encoded = 7 [default = false];
}

参考:

[1]我猜你有可能需要boost的知识
关于unique_lock
file:///C:/Program%20Files/boost_1_60_0/doc/html/thread/synchronization.html#thread.synchronization.mutex_types.mutex

关于同步机制的(Handling mutexes in C++)

[2]如果你安装了boost的文档,你可以在找到关于线程的知识
file:///C:/Program%20Files/boost_1_60_0/doc/html/thread/thread_management.html#thread.thread_management.this_thread.interruption_requested

[3]关于弱指针的知识

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值