本文已参与「新人创作礼」活动,一起开启掘金创作之路。
在实时检测中为了能够更高效地利用CPU性能,常需要使用到多线程并发处理,但由于实时检测中每一帧都在刷新内存,而多线程编程最大的难点就在于容易造成内存冲突,所以本文章将介绍以下关于如何实现在实时检测中多线程编程的方法。
简介
线程是操作系统能够进行CPU调度的最小单位,它被包含在进程之中,一个进程可包含单个或者多个线程。可以用多个线程去完成一个任务,也可以用多个进程去完成一个任务,它们的本质都相当于多个人去合伙完成一件事。
C++多线程并发: (简单情况下)实现C++多线程并发程序的思路如下:将任务的不同功能交由多个函数分别实现,创建多个线程,每个线程执行一个函数,一个任务就这样同时分由不同线程执行了。
C++11 新标准中引入了四个头文件来支持多线程编程,他们分别是 ,,,<condition_variable>和。这里简单介绍一下前三个:
:该头文主要声明了两个类, std::atomic 和 std::atomic_flag,另外还声明了一套 C 风格的原子类型和与 C 兼容的原子操作的函数。
:该头文件主要声明了 std::thread 类,另外 std::this_thread 命名空间也在该头文件中。
:该头文件主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard, std::unique_lock, 以及其他的类型和函数。
实现
关于如何实现多线程并发处理,我们需要解决三个问题:
1.赋予子线程任务
这个问题不大,只需要注意std::thread的用法即可,赋予线程任务常常使用的时std::thread的构造函数来实现的,函数的第一个参数为需要执行的任务,后面为对应的参数。
赋予任务时的第一个参数应该用的时函数的地址,可以选择传入静态函数或者传入实例化类成员函数的地址都行。
2.内存冲突
多线程最经典的问题就是不同线程同时访问或修改同一块内存,这样就很容易造成内存冲突从而报错。这里我们就需要用到互斥锁,并且为了尽可能减少相同内存的占用,我们可以将所需要用到的公共内存部分在加互斥锁后进行拷贝,拷贝完再解锁。并发的线程访问的内存为拷贝的那一部分,这样就相当于将同一份内存拷贝后分隔开,这也是解决问题的一个较为简单方便的方法。
在这里简单介绍以下互斥锁:
互斥锁的特点就是在加锁后访问的内存,其他线程无法进行干涉,只有当解锁后才能访问那一块内存。使用前需要引入头文件,使用起来很简单,只需要实例化后进行加锁和解锁:
static std::mutex Matlock;
// 加锁
Matlock.lock();
// 解锁
Matlock.unlock();
死锁问题
死锁就是多个线程争夺共享资源导致每个线程都不能取得自己所需的全部资源,从而程序无法向下执行。
产生死锁的四个必要条件:
- 互斥(资源同一时刻只能被一个进程使用)
- 请求并保持(进程在请资源时,不释放自己已经占有的资源)
- 不剥夺(进程已经获得的资源,在进程使用完前,不能强制剥夺)
- 循环等待(进程间形成环状的资源循环等待关系)
死锁避免: 对分配资源做安全性检查,确保不会产生循环等待。
3.实时检测中的线程管理
在图像实时检测时,假如我们每一帧都要开启新的线程并发运行,但由于主线程和子线程运行并不同步,就有可能出现主线程运行到下一次要开启新线程的时候,子线程还没有运行结束,此时重新赋予子线程新的任务就会导致程序运行失败。
解决方法:
在给子线程赋予新任务前将子线程与任务进行分离,保证在给子线程赋予新任务时子线程已经处于空闲状态。
实现起来很简单,只需要判断可以分离的时候加入detach()即可,即:
if (thread.joinable())
thread.detach();
注意
多线程编程的实现其实不难,难在其中的逻辑排列和内存使用。稍微不注意在计算机高速运行的情况下就有可能出现千千万万种错误。
需要注意的是并不是只要开了多线程并发处理,我们的程序运行速度就能够有所提升。当CPU占用率很高时,即使开了多线程,CPU也无法同时完成多个任务,此时多线程反而会降低程序的运行速度。