算法部分---线程类抽象C11

目录

一、C11线程

二、类抽象

1.头文件

2.cpp文件

2.1 创建thread,绑定loopFun

2.2 setFrame给模块

2.3 threadFun 方法

2.4 线程的退出

3.code解析

总结


一、C11线程

    C++11中提供了多线程的标准库,提供了管理线程、保护共享数据、线程间同步操作等类,比起pthread更加灵活,不易出错。但是并发执行需要消耗时间代价, 系统从一个任务切换到另一个任务需要执行一次上下文切换

多进程并发 缺点:
A: 进程间通信较为复杂,速度相对线程间的通信更慢。 B: 启动进程的开销比线程大,使用的系统资源也更多。
多进程并发 优点:
A: 进程间通信的机制相对于线程更加安全。  B: 能够很容易的将一台机器上的多进程程序部署在不同的机器上。
多线程并发优点:
A: 由于可以共享数据,多线程间的通信开销比进程小的多。B: 线程启动的比进程快,占用的资源更少。
多线程并发缺点:
A: 共享数据太过于灵活,为了维护正确的共享,代码写起来比较复杂。B: 无法部署在分布式系统上。

二、类抽象

1.头文件

    类使用的声明模块, 线程相关部分:锁住共享数据互斥锁mutex, 管理互斥量的lock_guard, 线程同步的condition_variable。子类具体实现的图像处理模块process()。

/*
*** Data:   2021.03.01
*** Author: xiaoqi.wang
*** Email:  xiaoqiwang@tusvisiontech.com
*/

#ifndef ALGORITHMBASE_H
#define ALGORITHMBASE_H


enum ALG_RESULT_TYPE {
    ALG_RESULT_TYPE_SUCCESS = 0,                  /* 算法处理结果正常 */
    ALG_RESULT_TYPE_ONE_FAIL = 1,                 /* ONE 工位检测失败 */
};

/* 算法处理结果上报 */
struct AlgoResult {
    int algorithm_id;                              /* 算法的id */ 
    std::map<int, cv::Mat> res_imgs;               /* 处理后的图像 */
    void* reserved;                                /* 预留 */ 
};

using SrcFrame = std::pair<int64_t, cv::Mat>;

//算法callback
class AlgoListener
{
public:
    virtual ~AlgoListener() {}
    virtual bool onResultFromAlgo(uint64_t time_stamp, AlgoResult& algo_result) = 0;
};


//算法基础类,线程同步相关
class AlgorithmBase
{
public:
    explicit AlgorithmBase(): process_running_(false) {}
    virtual ~AlgorithmBase() { }
    //... ...
    //camera下发frame,唤醒线程
    bool setFrameFromCamera(const cv::Mat& src_image, int64_t frame_id);
    //初始化
    virtual bool init() { return true;}
    //开始
    void start();
    //停止
    void stop();

protected:
    //受保护的方法和属性,子类可访问
    void process_loop();
    virtual bool process() = 0;

private:
    //类内和友元可访问,子类不可访问
    AlgoListener* listener_;
    std::mutex mutex_;
    std::condition_variable process_cond_;
    std::unique_ptr<std::thread> process_thread_;
    std::atomic<bool> process_running_;
};

#endif // ALGORITHMBASE_H

2.cpp文件

2.1 创建thread,绑定loopFun

绑定线程执行的函数

//创建thread
void AlgorithmBase::start()
{
    process_running_ = true;
    process_thread_ = std::unique_ptr<std::thread> (new std::thread(&AlgorithmBase::process_loop, this));

}

2.2 setFrame给模块

上层主动的调用, 发送数据

//camera下发frame,唤醒线程
bool AlgorithmBase::setFrameFromCamera(const cv::Mat& image, int64_t frame_id) {
    std::lock_guard<std::mutex> guard(mutex_);
    //process的list
    SrcFrame src_frame = std::pair<int64_t, cv::Mat>(frame_id, image);
    src_frames_.push_back(std::move(src_frame));
    //唤醒thread
    process_cond_.notify_one();

    return true;
}

2.3 threadFun 方法

被动的唤醒, 接收数据

//phread run 函数
void AlgorithmBase::process_loop()
{
    while (process_running_) {
        auto pred_func = [this] { return (!process_running_) || (src_frames_.size() > 0); };
        {
            std::unique_lock<std::mutex> lock(mutex_);
            while(true) {
                //pred_flag为False,阻塞该线程
                process_cond_.wait(lock, pred_func); 
                bool pred = true;
                //唤醒跳出循环
                if(!process_running_) {
                    return;
                } else if (pred) {
                    break;
                }
            }

            src_frame_ = src_frames_.front();
            src_frames_.pop_front();
        }

        //pipe line的处理, 子类具体实现
        process();

        //发送observer的时间戳
        struct timeval current_time;
        gettimeofday(&current_time, NULL);

        listener_->onResultFromAlgo((current_time.tv_sec*1000 +current_time.tv_usec/1000), algo_result_);
    }
}

2.4 线程的退出

//停止stop thread
void AlgorithmBase::stop()
{
    if (process_thread_) {
        process_running_ = false;
        process_cond_.notify_one();
        if(process_thread_->joinable()) {
            process_thread_->join();
        }
    }
}

3.code解析

3.1 std::mutex

C11最基本的互斥量,独占所有权的特性。lock()原语锁住该互斥量:

A: 没有被锁住,调用线程将该互斥量锁住,直道调用unlock。

B:被其它的线程锁住,当前的调用线程被阻塞。

C:被当前的线程锁住,产生死锁。

mutex m;
m.lock();
sharedVariable= getVar();
m.unlock();

3.2 std::lock_guard

mutex保证关键部分是顺序执行的, 如上代码: 关键部分异常或者忘记解互斥锁, 则会出现死锁。{ }之外即生命周期离开临界区时,调用析构函数简单的说: lock_guard在构造时加锁, 在离开时析构释放锁 。

{
  std::mutex m,
  std::lock_guard<std::mutex> lockGuard(m);
  sharedVariable= getVar();
}

3.3 std::unique_lock

比lock_guard更加灵活, 功能更加强大, 如延时try_lock等, 但是要付出更多的性能成本, 同时可以进行unlock和lock的操作。

void print_block (int n, char c) 
{
    //unique_lock有多组构造函数, 这里std::defer_lock不设置锁状态 
    std::unique_lock<std::mutex> my_lock (mtx, std::defer_lock); 
    //尝试加锁, 如果加锁成功则执行 
    //(适合定时执行一个job的场景, 一个线程执行就可以, 可以用更新时间戳辅助) 
    if(my_lock.try_lock()) { 
        for (int i=0; i<n; ++i) {
            std::cout << c; std::cout << '\n';
        } 
    } 
}
void shared_print(std::string msg, int id) {
    std::unique_lock<std::mutex> guard(_mu);
    //do something 1
    guard.unlock(); //临时解锁
    //do something 2
    guard.lock(); //继续上锁
    // do something 3
    // 结束时析构guard会临时解锁
    // guard.ulock(); // 这句话可要可不要,不写,析构的时候也会自动执行
}

3.4 std::condition_variable

条件变量允许使用通知而实现线程的同步, 可以作为发送者和使用者的角色。作为发送者, 可以通知一个或者多个接受者。

std::mutex mutex_;
std::condition_variable condVar;

void waitingForWork(){
    std::cout << "Worker: Waiting for work." << std::endl;

    std::unique_lock<std::mutex> lck(mutex_);
    condVar.wait(lck);
    //doTheWork();
    std::cout << "Work done." << std::endl;
}

void setDataReady(){
    std::cout << "Sender: Data is ready."  << std::endl;
    condVar.notify_one();
}

接收方在发送方发出通知之前完成了任务。接收方对虚假的唤醒很敏感所以即使没有通知发生,接收方也有可能会醒来,为了保护它,可等待方法添加一个判断。它接受一个判定。判定是个callable,它返回true或false。

void waitingForWork(){
    std::cout << "Worker: Waiting for work." << std::endl;

    std::unique_lock<std::mutex> lck(mutex_);
    condVar.wait(lck,[]{return dataReady;});
    //doTheWork();
    std::cout << "Work done." << std::endl;
}

3.5 std::pair

将两个数据合并到一个数据, 可以是不同的数据类型, 两个变量是first和second, 初始化时可以使构造函数也可以是std::make_pari() , make_pari() 在pari做参数位置, 不同是make_pari有隐式的类型转化。

std::pair<int, float>(1, 1.2);  std::make_pair(1, 1.2); //double类型

1.namespece std{
  template<typename T1, typename T2>
  struct pair{
    T1 first;
    T2 second;
    ...
  }
}

3.6 std::thread

A: 线程创建成功后立即启动,并没有一个类似start的函数来显式的启动线程。

B:  就需要显式的决定是要等待它完成(join),或者分离它让它自行运行(detach)。注意:只需要在std::thread对象销毁前做出这个决定。

C: join(),主线程会一直阻塞着,直到子线程完成,join()函数的另一个任务是回收该线程中使用的资源。

void function_1() {
    std::cout << "I'm function_1()" << std::endl;
}

std::thread t1(function_1);

调用detach(),从而将t1线程放在后台运行,所有权和控制权被转交给C++运行时库,以确保与线程相关联的资源在线程退出后能被正确的回收。

void test() {
    std::thread t1(function_1);
    t1.detach();
    // t1.join();
    std::cout << "test() finished" << std::endl;
}
// 使用 t1.detach()时
// test() finished
// I'm function_1()

// 使用 t1.join()时
// I'm function_1()
// test() finished

一旦一个线程被分离了,就不能够再被join了。如果非要调用,程序就会崩溃,可以使用joinable()函数判断一个线程对象能否调用join()

void test() {
    std::thread t1(function_1);
    t1.detach();

    if(t1.joinable())
        t1.join();

    assert(!t1.joinable());
}

3.7 std::move

A: 使用比如vector::push_back 等这类函数时,会对参数的对象进行复制,这就会造成对象内存的额外创建, 本来原意是想把参数push_back进去就行了。

B: 避免不必要的拷贝操作,为性能而生。std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝。

C:  实现 移动语义意味着两点, 原对象不再被使用,如果对其使用会造成不可预知的后果。所有权转移,资源的所有权被转移给新的对象。

D: std::move除了能实现右值引用,同时也能实现对左值的引用。在左值上使用移动语义。

template<typename T>
typename remove_reference<T>::type&& move(T&& t)
{
    return static_cast<typename remove_reference<T>::type &&>(t);
}

对于右值 首先模板类型推导确定T的类型为string,得remove_reference::type为string,故返回值和static的模板参数类型都为string &&;而move的参数就是string &&,于是不需要进行类型转换直接返回。

std::move(string("haha"));

对于左值 当将一个左值传递给一个参数是右值引用的函数,且此右值引用指向模板类型参数(T&&)时,编译器推断模板参数类型为实参的左值引用。

string str("xixixi");
std::move(str);

此时明显str是一个左值,首先模板类型推导确定T的类型为string &,得remove_reference::type为string。故返回值和static的模板参数类型都为string &&;而move的参数类型为string& &&,即为sting &。所以结果就为将string &通过static_cast转为string &&。返回string &&


总结

C11 线程类的抽象

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值