小目标检测(3)——msgqueue多线程通信和多线程互斥编程

news2024/11/12 8:50:10

文章目录

    • 引言
    • 正文
      • 代码的执行和线程
      • 使用std::mutex进行编程
        • mutex基本用法
          • std::lock_guard的使用
          • std::unique_lock的使用
      • condition_variable的使用
        • wait函数的使用
        • condition_variable的整体代码
      • 多线程编程的基本语句
    • 总结
    • 引用

引言

  • 在学习老师给的目标检测的代码过程中,接触到了串口通信、相机控制以及多线程通信。在前两个文章已经介绍了串口通信使用的Pcomm库,大恒相机的具体控制,具体链接如下:

    • 串口通信使用Pcomm库,链接
    • 大恒相机控制程序,链接
  • 在本文中,将具体介绍多线程的信息传递的具体实现。因为在最终的程序中,需要同时控制四个相机,并且要同时完成图片处理和控制的多个任务,所以必不可少,要使用多线程。程序中对于消息通信机制,是借鉴了Sogou C++ Workflow这个项目。

  • 这里就结合这个程序简单分析一下。

  • 这里主要是偏向文字的分析比较多,有一片有图的博客,可以参考一下

    • 基本信号量的解释
    • 读者写者问题的具体分析

正文

  • 这里使用了两个队列,一个是生产者放入消息的队列,一个是消费者取走消息的队列,两个队列进行互换,实现消息的传递。
  • 之前虽然接触过进程锁之类的编程,但是都是使用自己定义的结构体来实现进程的互斥访问,并没有使用过std::mutex进行编程,而且线程也是自己定义的一些类的实体。
  • 所以,这里首先介绍一下线程的基本要素,然后介绍一下std::mutex信号量类,接着在介绍一下std::condition_variable同步线程类的使用,最后在介绍具体的实现过程。

代码的执行和线程

  • 程序的编译执行过程

    • 编写源代码
    • 替换源代码中的预处理
    • 将源代码编译为汇编代码
    • 将汇编代码编译为目标代码
    • 将多个文件的目标代码进行链接
    • 将链接后的目标代码加载到内存中
    • CPU执行内存中的机器代码
    • 结束和清理分配资源
  • 同一个进程的多个线程是共享代码段、数据段、堆空间的,每一个线程有自己的栈空间,寄存器

  • 多个线程之间共享和不共享的存储设备

    • 代码段:所有线程共用一个代码段,所有的线程都执行相同的程序
    • 数据段:所有线程共享数据段中的全局变量和静态变量,一般控制线程通信的都是使用全局变量
    • 堆空间 :所有线程的空间分配都是来自于同一个堆空间,线程可以释放和分配堆上的内存。对于堆空间的合理管理,是避免线程冲突的一个主要的方面。
    • 栈空间线程都有自己私有的栈空间,所以局部变量是线程安全的,
      • 栈空间:用于存储局部变量和函数调用的返回地址。
    • 寄存器:每一个线程都有自己的寄存器集合的副本。
  • 控制进程互斥

    • 所以,如果要控制进程互斥,就得对他们共享的空间中声明变量,也就是将互斥信号量声明为全局变量,或者静态变量,因为这二者是共享。不能在函数内部声明互斥锁变量,因为每一个线程都有自己的栈空间,意味着每一个都有一个互斥锁副本,彼此并不会有任何影响。

使用std::mutex进行编程

  • std::mutex是C++标准库中的一个类,用于同步线程访问共享资源。是一个同步原语,用来保护共享数据免受多个线程同时访问
  • 注意,如果要通过mutex互斥锁来控制线程同步,一定要声明为全局变量

mutex基本用法

  • 锁定

    • 当一个线程锁定互斥锁时,其他试图锁定该互斥锁的进程将会被阻塞,知道拥有互斥锁的线程解锁。
  • 解锁

    • 拥有互斥锁的线程可以解锁他,其他线程锁定
  • mutex有两种方式实现对于互斥锁的使用,分别如下

    • 使用std::lock_guard:一旦锁定,就不能解锁,除非当前作用域的变量被销毁
    • 使用std::unique_lock:一旦锁定,除了等待这个作用域的变量自动销毁,还可以自己加上unlock解锁,实现在作用域内解锁
std::lock_guard的使用
  • 在下述代码中,碎语mutex进行声明对象时,会自动落锁,然后在下面的地方编辑代码,当结束方法时,会自动解锁。
  • 具体样例代码如下
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx; // 全局互斥锁
int shared_data = 0; // 共享资源

void increment() {
    std::lock_guard<std::mutex> lock(mtx); // 自动锁定互斥锁
    ++shared_data;
    std::cout << "Thread " << std::this_thread::get_id() << " incremented shared_data to " << shared_data << '\n';
} // 锁定的互斥锁在lock对象离开作用域时自动解锁

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join();
    t2.join();
    return 0;
}
  • 运行结果

在这里插入图片描述

std::unique_lock的使用
  • 不同于std::guard,std::unique_lock是需要手动落锁和解锁的,支持更加复杂的操作,支持多线程的通信操作。
  • 通过lock()落锁,通过unlock()解锁
  • 具体使用代码如下
std::mutex mtx;	// 必然是全局变量
std::unique_lock<std::mutex> lock(mtx); // 构造时自动锁定mtx
// 在此处访问受保护的共享资源
lock.unlock(); // 显式解锁

// ...

lock.lock(); // 显式重新锁定
// 在此处再次访问受保护的共享资源
// 析构时自动解锁mtx

condition_variable的使用

  • std::condition_variable是C++标准库中的一个类,用于同步线程,是的线程能够相互之间进行通信。这个是实现读这些这问题的根本,当信号量发生变化,要及时通知相关进程进行操作。
  • 当线程需要等待某个条件成立(或某个事件发生)时,它可以使用条件变量进入睡眠状态。当条件成立时,另一个线程可以使用条件变量通知等待的线程,使其醒来并继续执行。
  • 基本用法:
    • wait():通知进程进入睡眠状态,直到另外一个线程调用notify_one()notify_all()通知它醒来,同时释放传入的互斥锁,并且下次被唤醒之后,会从wait之后的语句开始执行
    • notify_one() 通知一个正在等待的线程,唤醒某一个线程
    • notify_all() 通知所有正在等待的线程,唤醒所有线程

wait函数的使用

  • 功能描述:
    • 释放传入的互斥线程锁
    • 使执行函数的线程陷入阻塞
    • 被唤醒的线程,将会冲被阻塞的地方继续执行
    • 被唤醒的线程将重新获得互斥锁
  • 参数
    • std::unique_lockstd::mutex ,传入的是进程锁的落锁语句。
  • 具体使用
  • 执行流程:
    • 申请了一个线程t1,执行函数waitForReady
    • 线程t1执行到第一句lock,会对互斥锁变量mtx落锁
    • 执行到wait,因为ready为false,就陷入阻塞
    • 主线程沉睡10秒钟,莫放在执行别的任务
    • 主线程执行setReady函数,将ready设置为true,并且唤醒等待的线程
    • 线程t1醒来了,重新从wait开始往下执行,直到结束
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;	// 声明全局变量,互斥线程锁
std::condition_variable cv;	//声明全局条件变量
bool ready = false;		// 声明共有资源

void waitForReady() {
		// 获取线程互斥锁,落锁,其余线程并不能访问
    std::unique_lock<std::mutex> lock(mtx);
    // 判定是否满足执行条件,默认为false,会陷入阻塞
    while (!ready) { 
    	  // 当前线程陷入阻塞,并且释放互斥锁
    	  // 线程被唤醒之后,会重新落锁,从此出开始执行
        cv.wait(lock);
    }
    std::cout << "Ready is true, continuing execution.\n";
}

void setReady() {
    std::unique_lock<std::mutex> lock(mtx);
    ready = true;
    std::cout << "everything is ready \n";
    cv.notify_one(); // 唤醒等待的线程
}

int main() {
    std::thread t1(waitForReady);
    std::cout<<"allocate the task to thread 1"<<std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(10)); // 模拟一些工作
    setReady();
    t1.join();
    return 0;
}
  • 执行效果

在这里插入图片描述

condition_variable的整体代码

  • 多个线程进程互斥操作具体运行程序,主要是显出一点,那就是进程执行的随机性

  • 具体使用代码如下

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void print_id(int id) {
		// 输出并打印编号
    std::unique_lock<std::mutex> lock(mtx);
    while (!ready) { // 如果条件不满足,则等待
        cv.wait(lock);
    }
    std::cout << "thread " << id << '\n';
}

void go() {
    std::unique_lock<std::mutex> lock(mtx);
    ready = true; // 改变条件
    cv.notify_all(); // 通知所有等待的线程
}

int main() {
    std::thread threads[10];
    for (int i = 0; i < 10; ++i)
        threads[i] = std::thread(print_id, i);

    std::cout << "10 threads ready to race...\n";
    go(); // 开始比赛

    for (auto &th : threads) th.join();

    return 0;
}
  • 执行效果如下

在这里插入图片描述

  • 结果分析
    • 这个程序和上面那个程序差不多,唯一的差异就是线程多了,由原先的一个线程变成了10个线程,而且执行的顺序是随机的,说明一个问题,那就是同时唤醒所有的进程,但是进程获取互斥锁的顺序并不是按照阻塞的顺序获取的,是随机获取的

多线程编程的基本语句

  • 通过学习上面的样例程序,仅仅知道多线程的互斥访问如何实现,但是并没有学习过多线程的基本变成,但是或多或少用到了,这里做一下总结。

  • 在C++中一般使用std::thread库进行创建和管理线程,通过创建thread对象,来实现对于线程的操作

1. 构造函数

  • std::thread有多个构造函数,允许你以不同的方式创建线程。最常用的构造函数接受一个函数指针或可调用对象,并将其作为新线程的入口点。
void myFunction(int x) {
    // 代码
}

int main() {
    std::thread myThread(myFunction, 42); // 传递参数给线程函数
}

2. 成员函数

  • std::thread提供了一些成员函数来管理线程的生命周期和行为。以下是一些常用的成员函数:

    • join(): 等待线程完成执行。如果线程已经完成,则立即返回。
    • detach(): 允许线程独立运行。调用后,线程对象不再代表实际的线程执行。
    • joinable(): 检查线程是否可以被join或detach。
    • get_id(): 返回线程的ID。
    • hardware_concurrency(): 返回可用的并发线程数。

总结

  • 对于多线程的编程,之前仅仅是在数据结构的课程设计上接触过,并没有真切接受过,这次算是有一个初步的接触了。
  • 之前专门写过读者写者问题,写过哲学家进餐问题,但是都没有具体实现过,实际应用起来,还是听不一样的。
  • chatGPT搜索能力还是很强的。

引用

  • chatGPT-plus
  • std::mutex的参考文档
  • std::condition_variable的参考文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/817950.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

红队攻防 | 解决HW被疯狂封IP姿势~(附下载)

本项目其实就是个简单的代理服务器&#xff0c;经过小小的修改。加了个代理池进来。渗透、爬虫的时候很容易就会把自己ip给ban了&#xff0c;所以就需要ip代理池了。 ProxyPool 爬虫代理IP池项目,主要功能为定时采集网上发布的免费代理验证入库&#xff0c;定时验证入库的代理…

排序算法(冒泡排序、选择排序、插入排序、希尔排序、堆排序、快速排序、归并排序、计数排序)

&#x1f355;博客主页&#xff1a;️自信不孤单 &#x1f36c;文章专栏&#xff1a;数据结构与算法 &#x1f35a;代码仓库&#xff1a;破浪晓梦 &#x1f36d;欢迎关注&#xff1a;欢迎大家点赞收藏关注 文章目录 &#x1f353;冒泡排序概念算法步骤动图演示代码 &#x1f34…

ED透明屏有哪些应用场景?

ED透明屏是一种新型的显示技术&#xff0c;它采用了电致变色技术&#xff0c;可以实现屏幕的透明显示。ED透明屏的出现&#xff0c;为我们的生活带来了许多便利和创新。 首先&#xff0c;ED透明屏可以应用于商业广告领域。 传统的广告牌往往是固定的&#xff0c;无法改变内容&…

浅入浅出MySQL事务

什么是事务 事务是由数据库中一系列的访问和更新组成的逻辑执行单元。 事务的逻辑单元中可以是一条SQL语句&#xff0c;也可以是一段SQL逻辑&#xff0c;这段逻辑要么全部执行成功&#xff0c;要么全部执行失败。 事务处理的基本原则是“原子性”、“一致性”、“隔离性”和…

vue 修改端口号

在根目录创建一个vue.config.js文件夹 module.exports {lintOnSave: false,devServer: {port: 3000,open: true} }运行后

降压IC 外置MOS DC48V转24V 3A 30V-80V转24V 3A 高压大功率

摘要&#xff1a;本文介绍了AH8A50QA降压IC外置MOS芯片方案&#xff0c;可将输入电压范围从30V至80V和9V至100V转换为24V输出&#xff0c;并提供最大3A的输出电流。该芯片方案采用了内置MOS管和QFN-20封装&#xff0c;适用于电动车和汽车车载充电源等高压大功率应用场景。 随着…

Cpp6 — 模板

模板&#xff1a;这里有一个概念&#xff1a;泛型编程---针对广泛的类型去写代码编程。之前都是针对具体的类型进行编程。 模板分为函数模板和类模板。 函数模板 当我们想要使用一个swap可以用作多种类型时&#xff0c;可以使用模板。这样我们就可以不使用重载&#xff0c;不…

商城小程序踩坑(一):iPhone 11、iPhoneX 等设备底部安全区域/小黑条适配

一、前言 这两天正在开发商城小程序-商品详情页&#xff0c;在做设备测试的时候突然发现详情页底部—— 购物车 和 购买区域在苹果手机上不适配&#xff0c;并且还存在小黑条。 底部功能没有办法正常使用。 如下图所示&#xff1a; 解决后效果&#xff0c;如下图所示&#xff…

Swagger之Hello World !

目录 ■1&#xff0e;前言・Swagger介绍 ■2&#xff0e;例子&#xff0c;如果基于Spring Boot项目&#xff0c;实现Swagger---非常简单 2&#xff0e;1&#xff0e;已有的SpringBoot项目 2&#xff0e;2&#xff0e;修改POM文件 2&#xff0e;3&#xff0e;添加Config文件…

html请求谷歌音频跨域问题(谷歌翻译接口)虚拟机ping不通google(下载谷歌音频、下载百度翻译音频)

文章目录 调用谷歌翻译接口&#xff0c;尝试了几种方案&#xff0c;都提示跨域不行第一种&#xff08;通过js代码获取音频文件的Blob对象&#xff0c;提示跨域了&#xff09;代码结果 第二种&#xff08;尝试新窗打开音频url&#xff0c;404&#xff0c;估计也是跨域了&#xf…

StarRocks Friends 广州站精彩回顾

上周六&#xff0c;StarRocks & Friends 活动在羊城广州成功举行&#xff0c;社区的小伙伴齐聚一堂&#xff0c;共同探讨了 StarRocks 在业界的应用实践和湖仓一体等热门话题。 本文总结了技术交流活动的关键内容和视频资料&#xff0c;感谢社区每一位小伙伴的支持和参与&…

如何提高接口测试覆盖率?

接口测试是测试系统组件间接口的一种测试。 接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。 测试的重点是要检查数据的交换&#xff0c;传递和控制管理过程&#xff0c;以及系统间的相互逻辑依赖关系等。 接口测试该如何提高测试的覆盖率呢&#…

PCL点云处理之最小二乘空间直线拟合(3D) (二百零二)

PCL点云处理之最小二乘空间直线拟合(3D) (二百零二) 一、算法简介二、实现代码三、效果展示一、算法简介 对于空间中的这样一组点:大致呈直线分布,散乱分布在直线左右, 我们可采用最小二乘方法拟合直线,更进一步地,可以通过点到直线的投影,最终得到一组严格呈直线分布…

软件测试一周面试十家公司,分享面试经历

从开始面试讲起&#xff0c;公司规模我分成5类&#xff1a;创业公司0-20人&#xff0c;小型公司20-40人&#xff0c;中小型50-99&#xff0c;中型公司100-499即将上市的那种&#xff0c;已上市公司100-499。 创业公司 第一个面试的那家创业公司特别坑&#xff0c;开始面试&am…

4年测试“我“该何去何从?测试还是测试开发?

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 4年测试&#xff…

秋招备战笔试Day2

目录 单选 1.A 派生出子类 B &#xff0c; B 派生出子类 C &#xff0c;并且在 java 源代码有如下声明&#xff1a; 2.下面代码将输出什么内容&#xff1a;&#xff08;&#xff09; 3.阅读如下代码。 请问&#xff0c;对语句行 test.hello(). 描述正确的有&#xff08;&…

vlan access, trunk, hybrid (tagged/untagged) 笔记

vlan 接口和配置 PVID&#xff08;port vlan ID&#xff09; 定义 pvid 主要目的&#xff1a; 当交换机接口收到没有 vlan tag 标签的包时&#xff0c;接口会将定义的 pvid 作为当前包的 vlan id。当对映 pvid vlan 的包&#xff0c;通过当前交换机接口发出时&#xff0c;接…

ADSelfService Plus:保护密码安全的最佳解决方案

密码安全是当今数字时代中至关重要的话题。随着互联网和信息技术的迅速发展&#xff0c;我们的生活变得越来越数字化&#xff0c;密码已成为我们生活中不可或缺的一部分。然而&#xff0c;随着各种网络威胁和黑客攻击不断增加&#xff0c;保护我们的密码变得越来越重要。 密码安…

测试工程师刚入职如何快速熟悉需求并输出测试用例?

刚入职第一天&#xff0c;早上办完入职&#xff0c;下午就就分配了测试任务&#xff0c;2个模块13条短信验证&#xff0c;2天内输出测试用例&#xff08;xmind输出功能点&#xff0c;excel书写业务流&#xff09;。测试负责人给我们快速讲了一下业务&#xff0c;在这过程中大概…

openssl/bn.h: No such file or directory

报错截图 解决方法 ubuntu apt install libssl-dev -y centos yum install openssl-devel -y