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

文章目录

    • 引言
    • 正文
      • 代码的执行和线程
      • 使用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 
#include 
#include 

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 
#include 
#include 
#include 
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 
#include 
#include 
#include 

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;
}
  • 执行效果如下

小目标检测(3)——msgqueue多线程通信和多线程互斥编程_第1张图片

  • 结果分析
    • 这个程序和上面那个程序差不多,唯一的差异就是线程多了,由原先的一个线程变成了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的参考文档

你可能感兴趣的:(小目标检测,C++,多线程,进程互斥,线程互斥,thread)