C++11 实现多线程(线程同步、通信)实例的解析

本博文由 youngpan1101 出品,转载请注明出处。
文章链接: http://blog.csdn.net/youngpan1101/article/details/72729735
作者: 宋洋鹏(youngpan1101)
邮箱: [email protected]


运行环境

  • ubuntu 14.04.4 64bit(通过 $ lsb_release -a 命令进行查看)
  • gcc : 4.9.4 (通过 $ gcc --version 命令进行查看 )
  • g++: 4.9.4 (通过 $ g++ --version 命令进行查看)

多线程流程框图

在主线程下有两个线程:跟踪检测,该工程主要功能是通过检测机制检测出目标后,将目标位置发送给跟踪线程进行跟踪,当然跟踪过程中会一直进行目标的检测,以免出现跟踪过程中的跟丢现象。

CMake 工程代码 【BaiduYun: Code Download】

  1. CMakeLists.txt 【Ubuntu 14.04 安装 OpenCV-3.2.0】

     cmake_minimum_required(VERSION 2.8)
     project( MultiThread )
    
     set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++11 -pthread")
    
     set(CMAKE_VERBOSE_MAKEFILE off)
    
     # 寻找OpenCV库
     # set(CMAKE_PREFIX_PATH "/home/hwj/3rdParty/OpenCV/v2.4.13/lib")
     # find_package( OpenCV 2.4.13 REQUIRED )
     find_package( OpenCV 3.2 REQUIRED )
    
     # 添加头文件
     include_directories( ${OpenCV_INCLUDE_DIRS} )
     include_directories( ${PROJECT_SOURCE_DIR} )
    
     add_executable( MultiThreadDemo main.cpp )
    
     add_library( ObjectTracker    SHARED  ObjectTracker.cpp )
     add_library( ObjectDectector  SHARED  ObjectDectector.cpp )
     add_library( RobotTracker     SHARED  RobotTracker.cpp )
    
     # 链接OpenCV库
     target_link_libraries( ObjectTracker   ${OpenCV_LIBS} )  
     target_link_libraries( ObjectDectector ${OpenCV_LIBS} )  
     target_link_libraries( RobotTracker  ObjectDectector ObjectTracker ${OpenCV_LIBS} )  
    
     target_link_libraries( MultiThreadDemo  RobotTracker)  
    • 若 gcc 版本是 4.8.4 可能会报错

      terminate called after throwing an instance of 'std::system_error'
      what():  Enable multithreading to use std::thread: Operation not permitted
      Aborted (core dumped)

      这是 gcc 的一个 bug,对于 ld 自动加上了 as-needed 选项,可以修改 CMakeLists.txt :

      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--no-as-needed -Wall -std=c++11 -pthread")

      另外一种解决方法就是升级 gcc 到版本 4.9.2,详细操作可以参考 【Ubuntu 14.04 中升级 gcc 到版本 4.9.2 并切换使用它们】

  2. Main.cpp

    通过读取视频流获取图像帧:

     #include "RobotTracker.h"
    
     int main(int argc, char** argv)  
     {        
        std::string filePath = "../hanwuji_robot.avi";  
        RobotTracker m_Tracker(filePath);   
        return 1;  
     }  
  3. RobotTracker.h

     #ifndef _ROBOT_TRACKER_H
     #define _ROBOT_TRACKER_H
    
     #include 
     #include 
     #include 
    
     #include 
     #include   // for std::thread
     #include    // for std::mutex
     #include    // for std::queue
     #include    // for std::condition_variable
     #include      // for std::bind
    
     #include "ObjectDectector.h"
     #include "ObjectTracker.h"
    
     typedef enum{
        RAW_IMAGE = 1,
        IMAGE_AFTER_DETECTOR_WITH_TARGET_BOX,
        IMAGE_AFTER_DETECTOR_WITHOUT_TARGET_BOX,
     }eImageFlag;
    
     typedef struct{
        cv::Mat    _Image;
        cv::Rect   _TargetBox;
        eImageFlag _ImageFlag;
     }stData;
    
     typedef enum{
        TRACKING_PREPARE = 1,  // preparation because of no given target bounding box to initial
        TRACKING_RUNNING,
     }eTrackerStatus;
    
     typedef enum{
        TRACKER_REQUIRE_REINITIAL_TARGET_BOX = 1,
        DETECTOR_FINISH_TARGET_BOX,
     }eThreadsStatus;
    
     class RobotTracker{
     public:
         RobotTracker(const std::string &VideoPath);
         ~RobotTracker();
    
     private:
         void ObjectTrackerThread(const std::string &VideoPath);
    
         void ObjectDetectionThread();
    
         bool isDataQueueEmpty();
         bool isDataQueueNotEmpty();
    
         void pushDataToQueue(const stData &Data);
    
         bool popDataFromQueue(stData &Data);
    
         eThreadsStatus getThreadsStatus();
         void setThreadsStatus(eThreadsStatus status);
    
         bool getExitTrackerStatus();
         void setExitTrackerStatus(bool status);
    
         std::string m_ImgWinName;
    
         std::mutex              m_Mutex;
         std::mutex              m_Mutex4Queue;
         std::mutex              m_Mutex4ThreadsStatus;
         std::mutex              m_Mutex4ExitTracker;
    
         std::queue      m_DataQueue;    // 用于线程间通信的队列   
         std::condition_variable m_ConVar; 
    
         eThreadsStatus  m_ThreadsStatus;
         bool m_isExitTracker;
    
         std::thread m_TrackerThread;   
         std::thread m_DetectorThread;
    
     };
    
     #endif
    • 类成员变量定义的顺序,mutex 和 condition_variable 必须在 thread 的前面:
      如果线程的定义在前面,线程初始化完成之后立马会执行线程函数,而线程函数里用到了 mutex 和condition_variable ,此时如果 mutex 和 condition_variable 还没初始化完成,就会出现内存错误
  4. RobotTracker.cpp

     #include "RobotTracker.h"
    
     RobotTracker::RobotTracker(const std::string &VideoPath)
     {
         std::cout << "RobotTracker construction" << std::endl;
         // ps_01
         m_TrackerThread = std::thread( std::bind(&RobotTracker::ObjectTrackerThread, this, VideoPath) );
         m_DetectorThread = std::thread( std::bind(&RobotTracker::ObjectDetectionThread, this) );
     }
    
     RobotTracker::~RobotTracker()
     {
         m_TrackerThread.join();   //ps_02
         m_DetectorThread.join();
         std::cout << "RobotTracker deconstruction" << std::endl;
     }
    
     void RobotTracker::ObjectTrackerThread(const std::string &VideoPath)
     {
         m_ImgWinName = (std::string)"RobotTracker";
         setExitTrackerStatus(false);
         setThreadsStatus(TRACKER_REQUIRE_REINITIAL_TARGET_BOX);    
    
         cv::namedWindow(m_ImgWinName, 1);  
         cv::Mat matTmp;
         ObjectTracker m_tracker(VideoPath);
    
         m_tracker.openCamera();
    
         if(m_tracker.getNextFrameFromOrbbecCamera(matTmp))
         {
             std::cout << "ObjectTrackerThread: The first frame " << std::endl;
    
             //ps_03 数据准备好后,使用 unique_lock 来锁定信号量,将数据插入队列之中 
             std::unique_lock<std::mutex> lockTmp(m_Mutex);  
    
             stData data;
             matTmp.copyTo(data._Image);
             data._TargetBox = cv::Rect(0,0,0,0);   
             data._ImageFlag = RAW_IMAGE;
             pushDataToQueue(data);   //ps_05
    
             // 通过条件变量通知其它等待的线程   
             std::cout << "ObjectTrackerThread: notify_all" << std::endl;  
             m_ConVar.notify_all();   //ps_04
             lockTmp.unlock();  
         }
    
         eTrackerStatus TrackStatus = TRACKING_PREPARE;
    
         stData stDataAfterDetection;
    
         while(1)
         {
             if(m_tracker.getNextFrameFromOrbbecCamera(matTmp))
             {
                 if(getThreadsStatus()==DETECTOR_FINISH_TARGET_BOX && popDataFromQueue(stDataAfterDetection))
                 {
                     m_tracker.initTracking(stDataAfterDetection._TargetBox);
                     TrackStatus = TRACKING_RUNNING;
    
                     stData stDataToPush;
                     matTmp.copyTo(stDataToPush._Image);
                     stDataToPush._TargetBox = cv::Rect(0,0,0,0);   
                     stDataToPush._ImageFlag = RAW_IMAGE;
                     pushDataToQueue(stDataToPush);
                     setThreadsStatus(TRACKER_REQUIRE_REINITIAL_TARGET_BOX);                
                 }
    
                 if(TrackStatus == TRACKING_RUNNING)
                 {
                     cv::rectangle(matTmp, m_tracker.updateTargetBoundingBox(), cv::Scalar(0, 0, 255), 2, 1);   // update ds-kcf tracking algorithm
                     std::this_thread::sleep_for(std::chrono::milliseconds(20));    // 休眠, unit:ms  
                 }
    
                 cv::imshow(m_ImgWinName, matTmp);  
                 int key = cv::waitKey(10);  
                 if(key == 27)
                 {
                     setExitTrackerStatus(true);
                     break;
                 }
             }
             else
             {
                 setExitTrackerStatus(true);
                 break;
             }
         }
     }
    
     void RobotTracker::ObjectDetectionThread()
     {
         std::cout << "ObjectDetectionThread:begin" << std::endl;  
    
         // 使用 unique_lock 来锁定信号量
         std::unique_lock<std::mutex> lockTmp_1(m_Mutex);  
         std::cout << "ObjectDetectionThread: before wait" << std::endl;  
         // ps_03 等待条件满足,unique_lock 和 std::function object,判断数据队列是否为空   
         #if 1   // method 1
         while(isDataQueueEmpty()) 
             m_ConVar.wait(lockTmp_1);
         #endif
         #if 0  // method 2
         m_ConVar.wait(lockTmp_1,std::bind(&RobotTracker::isDataQueueNotEmpty,this));   
         #endif
         #if 0  // method 3
         if(isDataQueueEmpty()) 
             m_ConVar.wait(lockTmp_1);
         #endif
         std::cout << "ObjectDetectionThread: pass wait" << std::endl;  
         // 解锁 mutex   
         lockTmp_1.unlock();  
    
         // cv::namedWindow 也会因为不同线程同时共享而报错,这里需要错开时间,或者使用 std::mutex
         std::string strWinName = "DetectorView";
         cv::namedWindow(strWinName, 1); 
         cv::moveWindow(strWinName, 710, 0);
    
         bool isStartDetection = false;
         ObjectDectector m_detector;
    
         while(1)
         {
             stData data;
    
             if(getExitTrackerStatus())
                 break;
    
             if(getThreadsStatus()==TRACKER_REQUIRE_REINITIAL_TARGET_BOX && popDataFromQueue(data))
             {
                 isStartDetection = true;
             }
    
             if(isStartDetection)
             {   
                 cv::Mat matTmp;
                 data._Image.copyTo(matTmp);
                 data._TargetBox = m_detector.getPersonDetectionBoundingBox(data._Image);
                 cv::rectangle(matTmp, data._TargetBox, cv::Scalar(0, 0, 255), 2, 1);  
                 cv::imshow(strWinName, matTmp);            
    
                 std::this_thread::sleep_for(std::chrono::milliseconds(400));    // 休眠, unit:ms  
                 data._ImageFlag = IMAGE_AFTER_DETECTOR_WITH_TARGET_BOX;
                 pushDataToQueue(data);
                 isStartDetection = false;
                 setThreadsStatus(DETECTOR_FINISH_TARGET_BOX);
             }
         }
     }
    
     bool RobotTracker::isDataQueueEmpty()
     {   
         std::lock_guard<std::mutex> lockTmp(m_Mutex4Queue);  
         std::cout << "RobotTracker::isDataQueueEmpty " << std::endl;    
         return m_DataQueue.empty();
     }
    
     bool RobotTracker::isDataQueueNotEmpty()
     {
         std::lock_guard<std::mutex> lockTmp(m_Mutex4Queue);  
         std::cout << "RobotTracker::isDataQueueNotEmpty " << std::endl;    
         return !m_DataQueue.empty();
     }
    
     void RobotTracker::pushDataToQueue(const stData &Data)
     {
         // ps_06 使用 lock_guard 来锁定信号量,将数据插入队列之中 
         std::lock_guard<std::mutex> lockTmp(m_Mutex4Queue);  
         std::cout << " pushDataToQueue " << std::endl;
         m_DataQueue.push(Data);  
     }
    
     bool RobotTracker::popDataFromQueue(stData &Data)
     {
         // 使用 lock_guard 来锁定信号量,将数据从队列中取出
         std::lock_guard<std::mutex> lockTmp(m_Mutex4Queue);  
         std::cout << " popDataFromQueue " << std::endl;
         if(m_DataQueue.size())
         {
             Data = m_DataQueue.front();  
             m_DataQueue.pop();  
             return true;
         }
         else
             return false;
     }
    
     eThreadsStatus RobotTracker::getThreadsStatus()
     {
         std::lock_guard<std::mutex> lockTmp(m_Mutex4ThreadsStatus);  
         return m_ThreadsStatus;
     }
    
     void RobotTracker::setThreadsStatus(eThreadsStatus status)
     {
         std::lock_guard<std::mutex> lockTmp(m_Mutex4ThreadsStatus);  
         m_ThreadsStatus = status;
     }
    
     bool RobotTracker::getExitTrackerStatus()
     {
         std::lock_guard<std::mutex> lockTmp(m_Mutex4ExitTracker);  
         return m_isExitTracker;
     }
    
     void RobotTracker::setExitTrackerStatus(bool status)
     {
         std::lock_guard<std::mutex> lockTmp(m_Mutex4ExitTracker);  
         m_isExitTracker = status;
     }
    • ps_01:【使用std::function和std::bind实现局部函数做回调】【C++11 学习笔记 std::function和bind绑定器】
      (1)std::bind 用来将可调用对象与其参数一起进行绑定。绑定后的结果可以使用 std::function 进行保存,并延迟调用到任何我们需要的时候。
      (2)【thread 的四种构造方式】
    • ps_02:【[C++11 并发编程] 02 - join】
      (1)join() 函数等待线程完成,若不等待线程完成,我们就需要确保该线程访问的数据都是有效的,直到该线程完成为止。若线程函数持有局部变量的指针或引用,当函数退出时,线程尚未执行完成。
      (2)对一个给定的线程,只能调用一次 join(),一旦调用了 join(),此 std::thread 对象不再是可连接的,如果调用其的 joinable() 将返回 false。
    • ps_03:【[C++11 并发编程] 11 - 线程间同步 - 等待一个消息或某种条件】
      (1)有些时候,我们需要在线程之间进行同步操作。一个线程等待另一个线程完成某项工作后,再继续自己的工作。使用C++标准库提供的机制-条件变量来实现等待操作。条件变量关联到某种事件或者其它条件,一个或多个线程可以通过这个变量来等待条件的发生。某个线程在发现该条件满足后,可以通知其它因等待该条件而挂起的线程,唤醒它们让它们继续执行。
      (2)wait() 中的条件变量在 wait 的时候会释放锁的,其他线程可以继续执行,代码中 method 1 和 method 2 的实现是一样的,method 3 在多核 CPU 下会存在虚假唤醒( spurious wakes)的情况,所以建议使用 method 1 ,while() 不仅仅在等待条件变量前检查条件变量,实际上在等待条件变量后也检查条件变量。,具体资料可以参考 【线程的虚假唤醒】
      (3) std::unique_lock 的灵活性还在于我们可以主动的调用 unlock() 方法来释放 mutex,因为锁的时间越长,越会影响程序的性能,在一些特殊情况下,提前释放 mutex 可以提高程序执行的效率,参考 【[C++11 并发编程] 08 - Mutex std::unique_lock】
    • ps_04:notify 第1种是 notify_one, 只唤醒一个在 wait 的线程; 第2种是 notify_all,唤醒所有在 wait 的线程。
    • ps_05:std::queue 用于线程间通信的队列, 【[C++11 并发编程] 11 - 线程间同步 - 等待一个消息或某种条件】
    • ps_06:由于同时有两个线程需要操作 m_DataQueue 成员变量,所以在读写的时候要加锁。
      (1)lock_guard 模板类使用 RAII 手法封装互斥量,在实例化对象的时候帮你加锁,并且能保证在离开作用域的时候自动解锁。参考 【thread 提供了四种不同的互斥量】

Build and Run

  1. Build

     $ cd build
     $ cmake ..
     $ make
  2. Run the project

    $ sudo ./MultiThreadDemo

Reference

  1. yamingwu CSDN : C++11 并发编程系列博客

  2. C++11 中的线程、锁和条件变量

    在主线程中处理辅助线程抛出的 C++ 异常和怎样在线程间传递异常。

  3. C++11 实现生产者消费者模式

  4. C++11 实现观察者模式

你可能感兴趣的:(编程开发)