C++实现线程池

2023-05-16

本文转载自:https://blog.csdn.net/caoshangpa/article/details/80374651

1、为什么需要线程池技术

    目前的大多数网络服务器,包括Web服务器、Email服务器以及数据库服务器等都具有一个共同点,就是单位时间内必须处理数目巨大的连接请求,但处理时间却相对较短。传统多线程方案中我们采用的服务器模型则是一旦接受到请求之后,即创建一个新的线程,由该线程执行任务。任务执行完毕后,线程退出,这就是是“即时创建,即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于不停的创建线程,销毁线程的状态。我们将传统方案中的线程执行过程分为三个过程:T1、T2、T3。
    T1:线程创建时间;
    T2:线程执行时间,包括线程的同步等时间;
    T3:线程销毁时间;
    那么我们可以看出,线程本身的开销所占的比例为(T1+T3) / (T1+T2+T3)。如果线程执行的时间很短的话,这比开销可能占到20%-50%左右。如果任务执行时间很长的话,这笔开销将是不可忽略的。
    除此之外,线程池能够减少创建的线程个数。通常线程池所允许的并发线程是有上界的,如果同时需要并发的线程数超过上界,那么一部分线程将会等待。而传统方案中,如果同时请求数目为2000,那么最坏情况下,系统可能需要产生2000个线程。尽管这不是一个很大的数目,但是也有部分机器可能达不到这种要求。
    因此线程池的出现正是着眼于减少线程本身带来的开销。线程池采用预创建的技术,在应用程序启动之后,将立即创建一定数量的线程(N1),放入空闲队列中。这些线程都是处于阻塞(Suspended)状态,不消耗CPU,但占用较小的内存空间。当任务到来后,缓冲池选择一个空闲线程,把任务传入此线程中运行。当N1个线程都在处理任务后,缓冲池自动创建一定数量的新线程,用于处理更多的任务。在任务执行完毕后线程也不退出,而是继续保持在池中等待下一次的任务。当系统比较空闲时,大部分线程都一直处于暂停状态,线程池自动销毁一部分线程,回收系统资源。基于这种预创建技术,线程池将线程创建和销毁本身所带来的开销分摊到了各个具体的任务上,执行次数越多,每个任务所分担到的线程本身开销则越小,不过我们另外可能需要考虑进去线程之间同步所带来的开销
     事实上,线程池并不是万能的。它有其特定的使用场合。线程池致力于减少线程本身的开销对应用所产生的影响,这是有前提的,前提就是线程本身开销与线程执行任务相比不可忽略。如果线程本身的开销相对于线程任务执行开销而言是可以忽略不计的,那么此时线程池所带来的好处是不明显的,比如对于FTP服务器以及Telnet服务器,通常传送文件的时间较长,开销较大,那么此时,我们采用线程池未必是理想的方法,我们可以选择“即时创建,即时销毁”的策略。
总之线程池通常适合下面的几个场合:

(1) 单位时间内处理任务频繁而且任务处理时间短;
(2) 对实时性要求较高。如果接受到任务后在创建线程,可能满足不了实时要求,因此必须采用线程池进行预创建。

2、代码实现(参考GitHub

    线程池的实现思想:“管理一个任务队列,一个线程队列,然后每次取一个任务分配给一个线程去做,循环往复。”

(1)头文件ThreadPool.h的内容如下

#ifndef THREAD_POOL_H
#define THREAD_POOL_H
 
#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>
 
class ThreadPool {

public:
    ThreadPool(size_t);                          //构造函数
    template<class F, class... Args>             //类模板
    auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type>;//任务入队
    ~ThreadPool();                              //析构函数

private:
    std::vector< std::thread > workers;            //线程队列,每个元素为一个Thread对象
    std::queue< std::function<void()> > tasks;     //任务队列,每个元素为一个函数对象    

    std::mutex queue_mutex;                        //互斥量
    std::condition_variable condition;             //条件变量
    bool stop;                                     //停止
};
 
// 构造函数,把线程插入线程队列,插入时调用embrace_back(),用匿名函数lambda初始化Thread对象
inline ThreadPool::ThreadPool(size_t threads) : stop(false){

    for(size_t i = 0; i<threads; ++i)
        workers.emplace_back(
            [this]
            {
                for(;;)
                {
                    // task是一个函数类型,从任务队列接收任务
                    std::function<void()> task;  
                    {
                        //给互斥量加锁,锁对象生命周期结束后自动解锁
                        std::unique_lock<std::mutex> lock(this->queue_mutex);
                        
                        //(1)当匿名函数返回false时才阻塞线程,阻塞时自动释放锁。
                        //(2)当匿名函数返回true且受到通知时解阻塞,然后加锁。
                        this->condition.wait(lock,[this]{ return this->stop || !this->tasks.empty(); });
                       
                         if(this->stop && this->tasks.empty())
                            return;
                        
                        //从任务队列取出一个任务
                        task = std::move(this->tasks.front());
                        this->tasks.pop();
                    }                            // 自动解锁
                    task();                      // 执行这个任务
                }
            }
        );
}
 
// 添加新的任务到任务队列
template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args) 
    -> std::future<typename std::result_of<F(Args...)>::type>
{
    // 获取函数返回值类型        
    using return_type = typename std::result_of<F(Args...)>::type;
 
    // 创建一个指向任务的只能指针
    auto task = std::make_shared< std::packaged_task<return_type()> >(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        );
        
    std::future<return_type> res = task->get_future();
    {
        std::unique_lock<std::mutex> lock(queue_mutex);  //加锁
        if(stop)
            throw std::runtime_error("enqueue on stopped ThreadPool");
 
        tasks.emplace([task](){ (*task)(); });          //把任务加入队列
    }                                                   //自动解锁
    condition.notify_one();                             //通知条件变量,唤醒一个线程
    return res;
}
 
// 析构函数,删除所有线程
inline ThreadPool::~ThreadPool()
{
    {
        std::unique_lock<std::mutex> lock(queue_mutex);
        stop = true;
    }
    condition.notify_all();
    for(std::thread &worker: workers)
        worker.join();
}
 
#endif

(2)基本使用方法

#include <iostream>
#include "ThreadPool.h"
 
int main()
{
    // create thread pool with 4 worker threads
    ThreadPool pool(4);
 
    // enqueue and store future
    auto result = pool.enqueue([](int answer) { return answer; }, 42);
 
    // get result from future, print 42
    std::cout << result.get() << std::endl; 
}

(3)另一个例子

#include <iostream>
#include "ThreadPool.h"
 
void func()
{
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::cout<<"worker thread ID:"<<std::this_thread::get_id()<<std::endl;
}
 
int main()
{
    ThreadPool pool(4);
    while(1)
    {
       pool.enqueue(fun);
    }
}

3、代码详细解释:

    首先我们看析构函数:inline ThreadPool::ThreadPool(size_t threads) : stop(false){},这个函数向线程队列worker插入threads个线程,每个线程对象用一个匿名函数lambda来初始化,每个匿名函数会一直循环着从任务队列中取出任务来执行。它首先对互斥量加锁,获得互斥锁后,调用条件变量的wait()函数等待条件发生,传入wait()函数的第二个参数为一个匿名函数lambda,当函数返回值为false时,wait会使得该线程阻塞(任务队列为空时会阻塞),并且对互斥量进行解锁。当函数返回值为true(任务队列不为空)并且收到条件变量的通知后,wait函数使线程解阻塞,并且对互斥量加锁。接着从任务队列里面弹出一个任务,对互斥量自动解锁,并执行这个任务。

    接着我们看添加任务的函数:auto ThreadPool::enqueue(F&& f, Args&&... args)-> std::future<typename std::result_of<F(Args...)>::type>{},这个函数向任务队列中加入一个任务。它首先创建一个名为task的智能指针,接着对互斥量加锁,然后把该任务加入到任务队列中,对互斥量自动解锁。调用条件变量的notify_one()函数,使得阻塞到这个条件变量的线程解阻塞。

    总结:初始化时,线程中的每个函数都会阻塞到条件变量那里,当任务队列中新加入一个任务时,通知阻塞到条件变量的某一个线程,接着这个线程执行:互斥量加锁——>任务队列出队——>互斥量解锁——>执行任务。当线程执行完任务之后,如果任务队列不为空,则继续从任务队列那里取出任务执行,如果任务队列为空则阻塞到条件变量那里。

  (1) std::result_of<func()>::type:获取func()函数返回值的类型;

  (2) typename:作用是告诉c++编译器,typename后面的字符串为一个类型名称,而不是成员函数或者成员变量;

  (3) 匿名函数lambda: 允许incline函数的定义被当做一个参数,用法为:[]()->type{},中括号内指定了传递方式(值传递或引用传递),小括号内指明函数的形参,->type指定了返回值类型,花括号内指定了函数体。

  (4) std::funtion<void()> :声明一个函数类型,接收任意原型是void()的函数、或函数对象、或是匿名函数。void() 意思是不带参数,没有返回值。

  (5) std::unique_lock<> : C++多线程编程中通常会对共享的数据进行写保护,以防止多线程在对共享数据成员进行读写时造成资源争抢导致程序出现未定义的行为。通常的做法是在修改共享数据成员的时候进行加锁--mutex。在使用锁的时候通常是在对共享数据进行修改之前进行lock操作,在写完之后再进行unlock操作,进场会出现由于疏忽导致由于lock之后在离开共享成员操作区域时忘记unlock,导致死锁。针对以上的问题,C++11中引入了std::unique_lock与std::lock_guard两种数据结构。通过对lock和unlock进行一次薄的封装,实现对象生命周期结束后自动解锁的功能,参考。用法如下:
    1)新建一个unique_lock对象 :std::mutex mymutex; 
    2)给对象传入一个std::mutex 对象作为参数:unique_lock lock(mymutex);

  (6) std::condition_variable:当 std::condition_variable 对象的某个 wait 函数被调用的时候,它使用 std::unique_lock(通过 std::mutex) 来锁住当前线程。当前线程会一直被阻塞,直到另外一个线程在相同的 std::condition_variable 对象上调用了 notification 函数来唤醒当前线程。
    std::condition_variable::wait() :std::condition_variable提供了两种 wait() 函数:
    1) 第一种情况:void wait (unique_lock<mutex>& lock);当前线程调用 wait() 后将被阻塞(此时当前线程应该获得了锁(mutex),不妨设获得锁 lock),直到另外某个线程调用 notify_* 唤醒了当前线程。在线程被阻塞时,该函数会自动调用 lock.unlock() 释放锁,使得其他被阻塞在锁竞争上的线程得以继续执行。另外,一旦当前线程获得通知(notified,通常是另外某个线程调用 notify_* 唤醒了当前线程),wait() 函数也是自动调用 lock.lock(),使得 lock 的状态和 wait 函数被调用时相同。
   2) 第二种情况:void wait (unique_lock<mutex>& lock, Predicate pred)(即设置了 Predicate),只有当 pred 条件为 false 时调用 wait() 才会阻塞当前线程,并且在收到其他线程的通知后只有当 pred 为 true 时才会被解除阻塞。
   std::condition_variable::notify_one() :唤醒某个等待(wait)线程。如果当前没有等待线程,则该函数什么也不做,如果同时存在多个等待线程,则唤醒某个线程是不确定的;

  (7) using return_type = typename xxx :指定别名,和typedef的作用类似;

  (8) std::make_shared<type> : 创建智能指针,需要传入类型;

  (9) std::future<> : 可以用来获取异步任务的结果,因此可以把它当成一种简单的线程间同步的手段;

  (10) std::packaged_task<> : std::packaged_task 包装一个可调用的对象,并且允许异步获取该可调用对象产生的结果,从包装可调用对象意义上来讲,std::packaged_task 与 std::function 类似,只不过 std::packaged_task 将其包装的可调用对象的执行结果传递给一个 std::future 对象(该对象通常在另外一个线程中获取 std::packaged_task 任务的执行结果);

  (11) std::bind() : bind的思想实际上是一种延迟计算的思想,将可调用对象保存起来,然后在需要的时候再调用。而且这种绑定是非常灵活的,不论是普通函数、函数对象、还是成员函数都可以绑定,而且其参数可以支持占位符,比如你可以这样绑定一个二元函数auto f = bind(&func, _1, _2);,调用的时候通过f(1,2)实现调用;

  (12) std::forward<F> : 左值与右值的概念其实在C++0x中就有了。概括的讲,凡是能够取地址的可以称之为左值,反之称之为右值,C++中并没有对左值和右值给出明确的定义,从其解决手段来看类似上面的定义,当然我们还可以定义为:有名字的对象为左值,没有名字的对象为右值。std :: forward有一个用例:将模板函数参数(函数内部)转换为调用者用来传递它的值类别(lvalue或rvalue)。这允许右值参数作为右值传递,左值作为左值传递,一种称为“完全转发”的方案;

  (13) std::packged_task::get_future: 获取与共享状态相关联的 std::future 对象。在调用该函数之后,两个对象共享相同的共享状态。

参考:https://www.cnblogs.com/haippy/p/3284540.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++实现线程池 的相关文章

  • 基于Zynq7020双千兆以太网的数字信号处理板设计

    一 背景 背景 Xilinx公司在2010年发布了可扩展的处理器平台Zynq7000系列 xff0c 它采用了28nm工艺 xff0c 将FPGA与ARM cortex A9集成在一颗芯片上 xff0c 实现了高性能 高集成度 低功耗 Zy
  • 深入理解JS中的变量作用域

    在 JS 当中一个变量的作用域 xff08 scope xff09 是程序中定义这个变量的区域 变量分为两类 xff1a 全局 xff08 global xff09 的和局部的 其中全局变量的作用域是全局性的 xff0c 即在 JavaSc
  • 硬件工程师,从零开始无人机开发。

    毕业已经五年了 xff0c 一直在杭州某大厂 xff0c 做无人机硬件开发 无人机这块 xff0c 我进去的时候大厂刚开始 做 xff0c 有幸参与到整个无人机的硬件开发 我这个刚毕业的技术小白 xff0c 在这五年间成长了很多 无奈 今年
  • 个人总结:板球控制系统之串级PID整定方法,速度环与位置环,40S任务10S完成

    其实单环我们先出了所有题目 xff0c 但是效果显然没有串级PID的效果好 xff0c 有人需要的话可以把程序包发出来 xff0c 板球运行视屏也有 另外 xff1a 天下舵机参差不齐 xff08 哪怕型号相同 xff09 xff0c 想要
  • 树莓派3B+踩坑记录:一、安装Ubuntu Mate

    树莓派3B 43 踩坑记录 xff1a 一 安装Ubuntu Mate 树莓派 xff0c Ubuntu xff0c ROS硬件准备软件准备系统烧录安装Ubuntu Mate更换国内源网络配置开启ssh远程其他彩虹屏解决方案XShell和X
  • PointNet代码详解

    PointNet代码详解 最近在做点云深度学习的机器人抓取 xff0c 这篇博客主要是把近期学习PointNet的一些总结的知识点汇总一下 PointNet概述详见以下网址和博客 xff0c 这里也就不再赘述了 三维深度学习之pointne
  • 卡尔曼滤波通俗易懂的解释

    关于卡尔曼滤波 xff0c 网上的资料很多 xff0c 但是有很大一部分都是不断堆叠公式 xff0c 然后用各种晦涩难懂的专业术语进行解释 xff0c 说实话我刚开始看的时候也是云里雾里 xff0c 因此写下这篇博客是为了照顾和我一样的萌新
  • STM32通过PWM控制ESC30C电调

    最近在搞一个水下推进器 xff0c 这东西的控制其实跟四旋翼的螺旋桨控制差不多 但我也是第一次用STM32板子来控制电调驱动桨叶旋转 xff0c 因此踩了很多坑 网上找了很多资料 xff0c 但是很多都写的不是很清楚 xff0c 这边稍微记
  • STM32F7同一定时器多路输出PWM波通道之间相互影响问题

    2020 8 12更新 这次用Cube直接生成PWM控制代码 xff0c 然后再RT Thread Studio上编写程序 xff0c 发现可实现TIM1和TIM8的8路PWM波调控 xff0c 因此上面论述的问题可能是自己在写底层时有某些
  • Ardusub源码解析学习(一)——Ardusub主程序

    APM Sub源码解析学习 xff08 一 xff09 Ardusub主程序 前言一 准备工作二 Ardusub cpp解析2 1 scheduler table2 2 schedulerget scheduler tasks setup
  • Ardusub源码解析学习(二)——电机库

    Ardusub源码解析学习 xff08 二 xff09 电机库学习 一 RC输入与输出1 1 RC Input1 2 RC Output 二 电机库学习2 1 setup motors 2 2 add motor raw 6dof 2 3
  • Ardusub源码解析学习(三)——车辆类型

    APM Sub源码解析学习 xff08 三 xff09 车辆类型 一 前言二 class AP HAL HAL三 class AP Vehicle3 1 h3 2 cpp 四 class Sub4 1 h4 2 cpp 五 总结 一 前言
  • 年度回忆录(2012.10----2013.01)

    寒假结束了 xff0c 年也过完了 xff0c 提前回来一天就开始着手补上这迟到的年终总结 xff0c 写了一个多星期还觉得有些东西没有写出来 xff0c 无奈 xff0c 点到为止吧 2012 年的后半年经历了很多 xff0c 收获了很多
  • Ardusub学习——飞行模式

    参考资料 xff1a Ardusb官方手册 Sub Rework joystick input and pilot input in general Flight Modes Ardusub支持多种飞行模式 xff0c 但是其中一部分需要有
  • Ardusub源码解析学习(五)——从manual model开始

    Ardusub源码解析学习 xff08 五 xff09 从manual model开始 manual init manual run 从本篇开始 xff0c 将会陆续对Ardusub中各种模式进行介绍 xff0c stabilize mod
  • 重读Ardupilot中stabilize model+MAVLINK解包过程

    APM源码和MAVLINK解析学习 重读stabilize stabilize modelinit run handle attitude MAVLINK消息包姿态信息传输过程 之前写的模式都是基于master版本的 xff0c 这次重读s
  • QGC添加自定义组件和发送自定义MAVLINK消息

    QGC添加自定义组件和发送自定义MAVLINK消息 一 添加自定义组件1 1 在飞行界面添加组件1 2 实现组件事件1 3 在MOCK模拟链接中实现验证1 4 验证 二 自定义MAVLINK消息的一些预备知识三 QGC自定义MAVLINK消
  • MAVLINK消息在Ardupilot中的接收和发送过程

    MAVLINK消息在Ardupilot中的接收和发送过程 SCHED TASKupdate receive update send 由于现在网上很多的都是APM旧版本的解释 xff0c 因此把自己的一些学习所得记录下来 截至写博客日期 xf
  • Ardupilot姿态控制器 PID控制流程

    Ardupilot姿态控制器 PID控制流程 一 PID姿态控制器1 1 Copter姿态控制官方原图1 2 ArduCopter V4 X STABILIZE 二 姿态控制器类实现2 1 类成员解析2 1 1 类成员变量2 1 2 类成员
  • APM姿态旋转理论基础

    APM姿态旋转理论基础 一 坐标系1 1 NED坐标系1 2 机体坐标系 二 欧拉角姿态变化率与机体角速度的关系 三 旋转矩阵3 1 基本公式3 2 矩阵作差3 3 旋转矩阵与变换矩阵的区别 四 DCM五 轴角法5 1 基本概念5 2 与旋

随机推荐

  • 详解APM的开方控制器sqrt_controller

    前言 前面说过 xff0c sqrt controller是对P项进行整定用途的 xff0c 目的就是让P项的控制响应 软 下来 xff0c 实际上就是一个经过改进的P控制器 读懂了sqrt controller xff0c 那么你对APM
  • Ardupilot前馈及平滑函数input_euler_angle_roll_pitch_yaw解析

    Ardupilot前馈及平滑函数input euler angle roll pitch yaw解析 源码解析这个函数做了什么部分细节euler accel limit input shaping angle 姿态变化率与机体角速度之间的关
  • Ardupilot倾转分离函数thrust_heading_rotation_angles

    Ardupilot倾转分离函数thrust heading rotation angles 什么是轴角分离源码分析一些细节补充效果显示及进一步修改 本文主要写一下自己对于APM倾转分离 xff08 轴角分离 xff09 函数的一些学习笔记及
  • Spring IOC原理解析

    首先恭喜守宏同学找到了自己心仪的工作 xff0c 入职的事情终于尘埃落定 xff0c 也算是一个新的开始吧 和守宏聊天的时候也说了很多有关工作的事情 xff0c 畅想了以后美好的未来 xff0c 也想到了今后的种种困难 不说别的就是单单在北
  • Ardupilot四元数姿态控制函数attitude_controller_run_quat解析

    Ardupilot四元数姿态控制函数attitude controller run quat解析 源码解析细节讲解thrust heading rotation angles update ang vel target from att e
  • Ardupilot速率控制器rate_controller_run解析

    Ardupilot速率控制器rate controller run解析 PID速率控制器源码解析rate controller run PID运算积分限制update i get ff set xxx 内容补充 xff1a 函数中陀螺仪数据
  • muduo网络库学习总结:基本架构及流程分析

    muduo网络库学习 xff1a 基本架构及流程分析 基本架构Basic ReactorMutiple Reactor 43 ThreadPool muduo库的基本使用基本结构介绍EventLoop类Poller类Channel类TcpC
  • push_back和emplace_back比较以及vector扩容

    push back和emplace back比较以及vector扩容 push back和emplace back的比较使用测试类测试过程将实体类对象传入将右值数字传入将实体类对象move 转右值之后传入 vector扩容过程 关于这部分内
  • 在ubuntu 11.04下编写驱动程序

    在ubuntu11 04下直接就可以编写驱动程序 xff0c 并进行编译 hello c include 34 linux init h 34 include 34 linux module h 34 static int hello in
  • ROS的优势与不足(除了ROS 机器人自主定位导航还能怎么做?)

    导读 xff1a 随着这两年国内机器人的升温 xff0c 自主定位导航技术作为机器人智能化的第一步正不断引起行业内的重视 为了实现这一功能 xff0c 不少厂家选择采用机器人操作系统ROS xff08 Robot Operation Sys
  • C++版本发展史

    1 C 43 43 98 2 C 43 43 03 3 C 43 43 11 3 1 nullptr 3 2 auto 3 3 decltype 3 4 初始化列表 3 5 范围for循环 3 6 右值引用 3 7 字符串字面量 3 8 n
  • 分布式数据库难题(三):数据一致性

    1 什么是数据一致性 一直以来 xff0c 在 分布式系统 和 数据库 这两个学科中 xff0c 一致性 xff08 Consistency xff09 都是重要概念 xff0c 但它表达的内容却并不相同 对于分布式系统而言 xff0c 一
  • 分布式数据库难题(四):单机事务

    1 ACID的含义 在数据库中 xff0c 事务 是由多个操作构成的序列 1970 年詹姆斯 格雷 xff08 Jim Gray xff09 提出了事务的 ACID 四大特性 xff0c 将广义上的事务一致性具化到了原子性 一致性 隔离性和
  • 对一个整数进行因式分解,求出所有质因数

    1 题目描述 给定一个正整数N xff0c 对N进行质因数分解 xff0c 求解N的所有质因数 2 解题思路 xff08 1 xff09 2 是很特殊的 xff0c 必须单独列出 xff08 2 xff09 必须先判断是否质数 因为如果是质
  • Windows10下安装Ubuntu18.04LTS详细教程

    这篇文章分享自己在Windows10系统下安装VMware虚拟机 xff0c 然后在VMware中安装Ubuntu 18 04 LTS的详细过程 之所以选择在虚拟机中安装Ubuntu xff0c 主要是可以不影响自己电脑的正常使用 xff0
  • 我的2011 写给小白

    许久前就想写这篇日志了 xff0c 但是一直以各种理由搪塞着 xff0c 没空闲 xff0c 再加上该死的期末考试 xff0c 唉 xff0c 真是愁煞人也 xff0c 现在好了 xff0c 什么都完事了 xff0c 也淡定了 xff0c
  • Pixhawk的历史

    发展历程 xff1a APM gt PX4FMU IO gt Pixhawk xff1a 1 Arduino简介 Arduino就是主要以以AVR单片机为核心控制器的单片机应用开发板 xff08 当然也有其他核心的例如STM32版本的但是不
  • 姿态解算基础:欧拉角、方向余弦、四元数

    什么是姿态解算 xff1a 飞行器的姿态解算过程涉及到两个坐标系 xff0c 一个是运载体的机体坐标系 xff0c 该坐标系与运载体固连 xff0c 当运载体转动的时候 xff0c 这个坐标系也跟着转动 xff0c 我们假设运载体的坐标系为
  • 姿态解算进阶:互补滤波(陀螺仪、加速度计、地磁计数据融合)

    互补滤波原理 xff1a 在四轴入门理论知识那节我们说 xff0c 加速度计和磁传感器都是极易受外部干扰的传感器 xff0c 都只能得到2维的角度关系 xff0c 但是测量值随时间的变化相对较小 xff0c 结合加速度计和磁传感器可以得到3
  • C++实现线程池

    本文转载自 xff1a https blog csdn net caoshangpa article details 80374651 1 为什么需要线程池技术 目前的大多数网络服务器 xff0c 包括Web服务器 Email服务器以及数据