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

2024-01-26 14:32
文章标签 算法 线程 部分 抽象 c11

本文主要是介绍算法部分---线程类抽象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(&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 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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/647102

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法)

求一段字符串中的最长回文串。 因为数据量比较大,用原来的O(n^2)会爆。 小白上的O(n^2)解法代码:TLE啦~ #include<stdio.h>#include<string.h>const int Maxn = 1000000;char s[Maxn];int main(){char e[] = {"END"};while(scanf("%s", s) != EO

秋招最新大模型算法面试,熬夜都要肝完它

💥大家在面试大模型LLM这个板块的时候,不知道面试完会不会复盘、总结,做笔记的习惯,这份大模型算法岗面试八股笔记也帮助不少人拿到过offer ✨对于面试大模型算法工程师会有一定的帮助,都附有完整答案,熬夜也要看完,祝大家一臂之力 这份《大模型算法工程师面试题》已经上传CSDN,还有完整版的大模型 AI 学习资料,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

dp算法练习题【8】

不同二叉搜索树 96. 不同的二叉搜索树 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。 示例 1: 输入:n = 3输出:5 示例 2: 输入:n = 1输出:1 class Solution {public int numTrees(int n) {int[] dp = new int

poj 2976 分数规划二分贪心(部分对总体的贡献度) poj 3111

poj 2976: 题意: 在n场考试中,每场考试共有b题,答对的题目有a题。 允许去掉k场考试,求能达到的最高正确率是多少。 解析: 假设已知准确率为x,则每场考试对于准确率的贡献值为: a - b * x,将贡献值大的排序排在前面舍弃掉后k个。 然后二分x就行了。 代码: #include <iostream>#include <cstdio>#incl

Codeforces Round #240 (Div. 2) E分治算法探究1

Codeforces Round #240 (Div. 2) E  http://codeforces.com/contest/415/problem/E 2^n个数,每次操作将其分成2^q份,对于每一份内部的数进行翻转(逆序),每次操作完后输出操作后新序列的逆序对数。 图一:  划分子问题。 图二: 分而治之,=>  合并 。 图三: 回溯: