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

chatgpt/2023/10/4 7:22:45

文章目录

    • 引言
    • 正文
      • 代码的执行和线程
      • 使用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.exyb.cn/news/show-5313931.html

如若内容造成侵权/违法违规/事实不符,请联系郑州代理记账网进行投诉反馈,一经查实,立即删除!

相关文章

Jenkins工具系列 —— Jenkins 安装并启动

文章目录 安装涉及相关链接选择安装Jenkins版本安装JenkinsJenkins web页面启动 安装涉及相关链接 Jenkins官网&#xff1a; https://www.jenkins.io/zh/ Jenkins下载安装步骤&#xff1a; https://www.jenkins.io/zh/download/ 安装各种版本OpenJDK&#xff1a; https://blog…

Scala关键字lazy的见解

Scala中使用关键字lazy来定义惰性变量&#xff0c;实现延迟加载(懒加载)。 惰性变量只能是不可变变量&#xff0c;并且只有在调用惰性变量时&#xff0c;才会去实例化这个变量。 在Java中&#xff0c;要实现延迟加载(懒加载)&#xff0c;需要自己手动实现。一般的做法是这样的…

SHELL——备份脚本

编写脚本&#xff0c;使用mysqldump实现分库分表备份。 1、获取分库备份的库名列表 [rootweb01 scripts]# mysql -uroot -p123456 -e "show databases;" | egrep -v "Database|information_schema|mysql|performance_schema|sys" mysql: [Warning] Using …

java: 无法访问redis.clients.jedis.JedisPoolConfig

问题描述: 在编译java springboot程序的时候报错 java: 无法访问redis.clients.jedis.JedisPoolConfig 找不到redis.clients.jedis.JedisPoolConfig的类文件 问题分析 该问题是由于找不到JedisPoolConfig包导致的,很可能是没有添加相关的依赖 问题解决 在pom文件中添加依赖项…

api自动化测试

API测试已成为日常的测试任务之一&#xff0c;为了提高测试效率&#xff0c;减少重复的手工操作&#xff0c;API自动化测试也逐渐变得愈加重要&#xff0c;本文是自己在API自动化测试方面的一些经验积累和心得、汇总成文&#xff0c;以飨读者 我相信自动化技能已经成为高级测试…

Spring优雅的在事务提交/回滚前后插入业务逻辑

业务背景 业务那边想要统计下我们这边每天注册商户成功和失败的数量&#xff0c;你看看怎么给他弄下这个功能 功能实现 TransactionSynchronizationManager.registerSynchronization&#xff0c;发现这是spring事务提供的注册回调接口的方法。 在事务注解方法中&#xff0c…

30天p小白学python-第三天(面向对象编程基础-进阶)

面向对象编程基础 活在当下的程序员应该都听过"面向对象编程"一词,也经常有人问能不能用一句话解释下什么是"面向对象编程",我们先来看看比较正式的说法。 "把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class)…

ChatGPT结合知识图谱构建医疗问答应用 (一) - 构建知识图谱

一、ChatGPT结合知识图谱 在本专栏的前面文章中构建 ChatGPT 本地知识库问答应用&#xff0c;都是基于词向量检索 Embedding 嵌入的方式实现的&#xff0c;在传统的问答领域中&#xff0c;一般知识源采用知识图谱来进行构建&#xff0c;但基于知识图谱的问答对于自然语言的处理…
推荐文章