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

来源:互联网 发布:linux nginx压力测试 编辑:程序博客网 时间:2024/06/04 00:49

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


运行环境

  • 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 permittedAborted (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 <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <iostream> #include <thread>  // for std::thread #include <mutex>   // for std::mutex #include <queue>   // for std::queue #include <condition_variable>   // for std::condition_variable #include <functional>     // 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<stData>      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 实现观察者模式

阅读全文
0 0
原创粉丝点击