本文主要是介绍算法部分---线程类抽象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_Henum 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的listSrcFrame src_frame = std::pair<int64_t, cv::Mat>(frame_id, image);src_frames_.push_back(std::move(src_frame));//唤醒threadprocess_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(¤t_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 1guard.unlock(); //临时解锁//do something 2guard.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 线程类的抽象
这篇关于算法部分---线程类抽象C11的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!