『 Linux 』基于阻塞队列的生产者消费者模型

news2024/9/21 14:50:53

文章目录

    • 生产者-消费者模型概述
    • 生产者消费者模型的高效性
    • 虚假唤醒
    • 信号丢失
    • 生产者消费者模型的模拟实现
    • 参考代码


生产者-消费者模型概述

请添加图片描述

生产者消费者模型是一种多线程设计模式,常见于解决多个生产者线程和多个消费者线程之间如何安全有效地共享数据;

该模型中存在三种关系,两个角色和一个交易场所;

两种角色分别为 消费者生产者 ;

  • 生产者

    生产者用于生产数据或任务,并将其放入共享区域中;

  • 消费者

    消费者负责从共享区域中读取数据或任务并进行处理;

一个交易场所指的是一块特定结构的内存空间,该区域用于充当生产者和消费者之间的中介,用于暂存数据,其中该空间可以是有限的也可以是无限的;

三种关系分别为 生产者与生产者 , 消费者与消费者 , 生产者与消费者 ;

  • 生产者与生产者

    生产者之间必须是互斥关系;

    多个生产者同时向共享空间写入数据时,需要互斥访问以避免共享空间状态的竞争和数据损坏;

    可通过互斥锁确保同一时刻只能有一个生产者向共享空间中写入数据;

  • 消费者与消费者

    消费者之间必须是互斥关系;

    多个消费者同时从缓冲区中读取数据时需要互斥访问以避免共享空间状态的竞争和数据损坏;

    可通过互斥锁确保同一时刻只有一个消费者可以从共享空间读取数据;

  • 生产者与消费者

    生产者与消费者需要既存在互斥关系也存在同步关系;

    • 互斥关系

      生产者和消费者都需要互斥的访问共享空间以避免数据竞争和数据不一致;

      通过互斥锁确保当一个线程(生产者或消费者)正在访问共享空间时其他线程不能同时访问;

    • 同步关系

      生产者和消费者需要在某些条件下等待对方的操作完成;

      例如当共享空间中数据高与一定数量时生产者需要等待消费者消费数据,当共享空间内数据低于一个数量时消费者需要等待生产者生产数据;

      需通过条件变量实现线程之间的同步,使生产者和消费者在需要等待时等待并在条件满足时被唤醒;

这种模型的设计的优点为:

  • 支持忙闲不均

    生产者和消费者可以以不同的速率进行工作,例如生产者写入数据或任务的速率大于消费者或者相反;

    其中共享空间使得生产者和消费者的速率不必严格匹配从而增强了系统应对负载波动的能力;

  • 对生产者和消费者进行解耦

    解耦意味着生产者和消费者不需要直接相互依赖或协调,他们通过共享缓冲区间接相互交互;

    不需要直接依赖对方的实现,是系统更加模块化和灵活,同时易于拓展和维护;


生产者消费者模型的高效性

请添加图片描述

生产者消费者模型是一种高效的设计模型;

其高效性不体现在于在加锁时生产者和消费者对共享资源的串型访问;

而是对于生产者生产数据前需先接收数据,消费者在消费数据后需要对数据进行加工处理;

本质上是对于非临界资源的处理,即可能当生产者在接收数据(访问非临界资源)时消费者正在消费数据(访问临界资源),或是生产者在生产数据(访问临界资源)时,消费者在处理加工数据(访问非临界资源);

而对于非临界资源的访问与处理也是具有时间开销的;

当一个生产者或是消费者正在访问非临界资源时不影响对端访问临界资源从而并发操作,提高了模型整体的高效性;

主要体现在以下方面:

  • 解耦生产和消费过程允许生产和消费以不同速率进行
  • 通过缓冲区平衡生产和消费的速度差异提高整体吞吐量
  • 允许生产者和消费者在各自的非临界区并行工作(一端访问临界,一端访问非临界)
  • 支持多个生产者和消费者并发操作进一步提高并行度


虚假唤醒

请添加图片描述

虚假唤醒指的是一个线程在没有收到明确的唤醒信号的情况下从条件变量的等待状态中被唤醒;

这种唤醒不是由程序逻辑触发的而是由系统或底层实现导致的;

void *threadRouding(void *args){
    // ... 其他操作
    pthread_mutex_lock(&mutex_);
    if (条件不满足) {
      pthread_cond_wait(&c_cond_, &mutex_);
    }
    // ... 其他操作
    pthread_mutex_unlock(&mutex_);
}

以该段代码为例,在多线程环境中,线程在进入函数时首先获取互斥锁,利用if判断是否条件不满足,若是条件不满足则会加入至条件变量的等待队列中;

假设共享资源不停在变化,在某一刻时使用pthread_cond_broadcast()唤醒了所有线程后,此时应只有一个线程成功获取锁并向下执行;

而其他线程被唤醒后应因条件不满足而继续等待,但在该段代码中若是其他线程也被唤醒后不会再次对条件变量条件状态进行检查而直接向下执行,此时其他线程则是一种虚假唤醒;

或者是共享资源在不停变化,当一个线程被pthread_cond_signal()唤醒时其共享资源中的状态已经不满足条件变量状态,但此时并未对条件变量状态进行重新检查,当线程被唤醒后向下执行时共享资源的状态已经发生了变化从而导致虚假唤醒;

故在使用条件变量来判断共享资源状态时应用while()循环来判断,使得当一个或若干个线程被虚假唤醒时能循环判断条件是否满足而决定是否继续向下执行;


信号丢失

请添加图片描述

信号丢失是指一个线程向一个条件变量发送线程唤醒信号时其条件变量中的等待队列并不含任何线程;

当该条件变量中的等待队列进入线程时该信号已经丢失,从而导致在条件应唤醒对应线程时没有任何线程能够接受到对应的唤醒信号从而依旧保持等待状态;

// 线程A(信号发送者)
pthread_mutex_lock(&mutex);
// 改变共享状态
pthread_cond_signal(&cond);  // 发送信号,但此时可能没有线程在等待
pthread_mutex_unlock(&mutex);

// 线程B(潜在的接收者,但尚未进入等待状态)
// ... 一些其他操作 ...
pthread_mutex_lock(&mutex);
while (!condition) {
    pthread_cond_wait(&cond, &mutex);  // 当这里执行时,信号可能已经发送并丢失
}
pthread_mutex_unlock(&mutex);

以该段代码为例,其中 线程A 因条件变量条件满足时向条件变量发送状态表示条件满足需要唤醒其中一个或多个线程;

但此时条件变量中的等待队列中不含有任何线程;

信号丢失一般情况下会发生在线程在进入等待队列中的这个时间间隙,没有及时进入等待队列导致信号丢失;

  • 信号丢失与虚假唤醒的区别

    • 信号丢失

      信号丢失是有实际信号发出,但是没有线程成功接收;

    • 虚假唤醒

      虚假唤醒是线程在没有实际唤醒信号的情况下被唤醒;

信号丢失可能导致程序死锁或功能错误;

在设计时使用条件变量的多线程程序时必须考虑并防止信号丢失;


生产者消费者模型的模拟实现

请添加图片描述

以单消费者单生产者为例:

/* BlockQueue.hpp */


#ifndef BLOCK_QUEUE_HPP
#define BLOCK_QUEUE_HPP

#include <pthread.h>
#include <unistd.h>

#include <iostream>
#include <queue>
template <class T>
class BlockQueue {
  static const int defaultnum = 10; // 设置初始最大容量

 public:
  BlockQueue(int maxcap = defaultnum) : maxcap_(maxcap) {
    pthread_mutex_init(&mutex_, nullptr);
    pthread_cond_init(&c_cond_, nullptr);
    pthread_cond_init(&p_cond_, nullptr);
    low_water_ = maxcap_ / 3;
    hight_water_ = maxcap_ * 2 / 3;
  }

  T pop() {  // 消费 Consumer
    pthread_mutex_lock(&mutex_);
    while (q_.size() == 0) { // 使用 while 循环防止线程虚假唤醒
      pthread_cond_wait(&c_cond_, &mutex_);
    }
    T out = q_.front();
    q_.pop();
    pthread_cond_signal(&p_cond_);

    pthread_mutex_unlock(&mutex_);

    return out;
  }

  void push(const T& in) {  // 生产 Productor
    pthread_mutex_lock(&mutex_); // 访问临界资源前进行上锁
    while (q_.size() == maxcap_) { // 进行条件判断 条件满足时
      pthread_cond_wait(&p_cond_, &mutex_);
    }
    q_.push(in);
    pthread_cond_signal(&c_cond_);

    pthread_mutex_unlock(&mutex_);
  }

  ~BlockQueue() {
    pthread_mutex_destroy(&mutex_);
    pthread_cond_destroy(&c_cond_);
    pthread_cond_destroy(&p_cond_);
  }

 private:
  std::queue<T> q_;  // 表示临界资源
  /* (STL容器不被保护 用户若是需要保护则需要自己上锁) */

  int maxcap_;  // 极值 表示该队列所能容纳的最大数据量

  pthread_mutex_t mutex_;  // 用于保护临界资源的互斥锁
  pthread_cond_t c_cond_;  // 条件变量
  pthread_cond_t p_cond_;

  int low_water_;    // 低水平位线
  int hight_water_;  // 高水平位线
  /*
    可通过加入水平位线条件控制生产者和消费者的生产消费策略
    如资源大于多少时通知消费者消费
    资源小于多少时通知生产者生产
   */
};
#endif

  • 类的定义

    • BlockQueue<T>

      一个模板类,实现了一个线程安全的阻塞队列;

    • T

      队列中存储的元素类型;

  • 成员变量

    • q_

      一个STL队列容器,用于存储数据;

    • maxcap_

      一个极值,用于表明当前队列的最大容量;

    • mutex_

      互斥锁,用于保护临界资源;

    • c_cond_

      消费者条件变量,用于将不满足条件的消费者线程加载进对应的等待队列中;

    • p_cond_

      生产者条件变量,用于将不满足条件的生产者线程加载进对应的等待队列中;

    • low_water_hight_water_

      高低水平位线,可定义控制生产和消费策略,如容器数据大于多少时通知消费者消费,容器数据小于多少时通知生产者生产(该代码中未使用);

  • 构造函数

    初始化互斥锁和条件变量,设置最大容量和水平位线;

  • pop()

    该函数用于消费者进行消费动作,即从队列中获取一个数据;

    • 获取当前互斥锁
    • 队列为空时等待消费者条件变量
    • 取出队首元素并返回
    • 发信号给生产者条件变量通知生产者生产
    • 解锁互斥锁
  • push()

    该函数用于生产者进行生产动作,即将数据加入至队列容器中;

    • 获取互斥锁
    • 当队列满时等待生产者条件变量
    • 将元素加入队列
    • 发送信号给消费者条件变量通知消费者消费
    • 解锁互斥锁
  • 析构函数

    销毁互斥锁和条件变量;

该段代码为该生产者消费者模型的核心代码,数据类型既可传入内置类型也可传入自定义类型完成一系列任务;

假设存在一个任务类:

/* Task.hpp */

#ifndef TASK_HPP
#define TASK_HPP
#include <iostream>

// 定义错误代码枚举
enum { DIV_ERR = 1, MOD_ERR, NONE };

class Task {
 public:
  // 构造函数:初始化所有成员变量
  Task(int num1, int num2, char oper)
      : num1_(num1), num2_(num2), exit_code_(0), result_(0), oper_(oper) {}

  // 析构函数(当前为空)
  ~Task() {}

  // 执行任务的主要函数
  void run() {
    switch (oper_) {
      case '+':
        result_ = num1_ + num2_;
        break;
      case '-':
        result_ = num1_ - num2_;
        break;
      case '*':
        result_ = num1_ * num2_;
        break;
      case '/': {
        if (num2_ == 0) {
          exit_code_ = DIV_ERR;  // 设置除零错误
          result_ = -1;          // 除零时结果设为-1
        } else
          result_ = num1_ / num2_;
        break;
      }
      case '%': {
        if (num2_ == 0) {
          exit_code_ = MOD_ERR;  // 设置模零错误
          result_ = -1;          // 模零时结果设为-1
        } else
          result_ = num1_ % num2_;
        break;
      }
      default:
        exit_code_ = NONE;  // 未知操作符
        break;
    }
  }

  // 重载()运算符,使对象可以像函数一样被调用
  void operator()() { run(); }

  // 获取计算结果
  int getresult() { return result_; }

  // 获取退出代码
  int getexitcode() { return exit_code_; }

  // 获取第一个操作数
  int getnum1() { return num1_; }

  // 获取第二个操作数
  int getnum2() { return num2_; }

  // 获取操作符
  char getoper() { return oper_; }

 private:
  int num1_;      // 第一个操作数
  int num2_;      // 第二个操作数
  int exit_code_; // 退出代码,用于表示操作是否成功
  int result_;    // 计算结果
  char oper_;     // 操作符
};

#endif

这段代码定义了一个Task类,用于表示和执行简单的算术运算任务;

该类包含两个操作数和一个操作符,计算结果和退出代码;

构造函数初始化所有成员变量;

run()方法是核心功能,将根据操作符执行相应的算术运算;

对于除法和取模特别处理了除数为0的情况并设置相应的错误码;

重载了()运算符进行仿函数设置;

提供了几个getter方法来访问私有成员变量;

对应的测试函数为如下:

#include <string>
#include "BlockQueue.hpp"
#include "Task.hpp"
using namespace std;

#include <unistd.h>
#include <ctime>

// 定义可能的运算符
string opers = "+-*/%";

// 消费者线程函数
void *Consumer(void *args) {
  // 将传入的参数转换为 BlockQueue<Task> 指针
  BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);
  while (true) {
    // 从队列中取出一个任务
    Task task = bq->pop();
    // 执行任务
    task();
    // 打印任务执行结果
    printf("The thread-%3lu handled a task , %2d %c %2d = %3d , exit code : %d\n",
           pthread_self() % 1000, task.getnum1(), task.getoper(),
           task.getnum2(), task.getresult(), task.getexitcode());
    cout << "------------------------------------" << endl;
  }
  return nullptr;
}

// 生产者线程函数
void *Productor(void *args) {
  int len = opers.size();
  // 将传入的参数转换为 BlockQueue<Task> 指针
  BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);
  while (true) {
    // 休眠0.5秒,控制生产速度
    usleep(500000);
    // 随机生成两个操作数和一个运算符
    int data1 = rand() % 10;
    int data2 = rand() % 10;
    char op = opers[rand() % len];
    // 创建新任务
    Task t(data1, data2, op);
    // 将任务推入队列
    bq->push(t);
    // 打印生成的任务信息
    printf("The thread-%3lu push a task ,%2d %c %2d = ? \n", pthread_self() % 1000,
           data1, op, data2);
  }
  return nullptr;
}

int main() {
  // 初始化随机数生成器
  srand(time(nullptr));
  
  // 创建一个 BlockQueue<Task> 对象作为共享队列
  BlockQueue<Task> *bq = new BlockQueue<Task>();
  
  // 声明线程ID变量
  pthread_t c_tid, p_tid;
  
  // 创建消费者线程
  pthread_create(&c_tid, nullptr, Consumer, bq);
  
  // 创建生产者线程
  pthread_create(&p_tid, nullptr, Productor, bq);

  // 等待线程结束(实际上是无限等待)
  pthread_join(c_tid, nullptr);
  pthread_join(p_tid, nullptr);
  
  // 清理资源(实际上不会执行到这里)
  delete bq;
  return 0;
}

该函数主要用于测试生产者消费者模型,其中生产者消费者所生产与消费的数据为Task任务;

  • 头文件和局部变量

    包含了必要的头文件以及定义的"BlockQueue.hpp""Task.hpp";

    定义了一个string对象为opers包含所有可能的运算符;

  • 消费者函数Consumer

    在接收一个指向BlockQueue<Task>的指针作为参数;

    在一个无限循环中进行以下动作:

    • 从队列中取出一个任务(bq->pop())
    • 执行任务(task())
    • 打印任务执行结果,包括线程id,操作数,运算符,结果以及退出码
  • 生产者函数Productor

    接收一个指向BlockQueue<Task>的指针;

    在一个无限循环中进行以下动作:

    • 每隔0.5susleep(500000)生成一个新任务
    • 随机生成两个09的操作数和一个随机运算符
    • 创建一个新的Task对象并将其推入队列(bq->push(t))
    • 打印生成的任务信息
  • 主函数main

    初始化一个随机数生成器(种一个随机数种子);

    创建一个BlockQueue<Task>对象同时创建一个消费者线程和一个生产者线程并向其传入BlockQueue指针;

    等待两个线程结束(此处为无限循环,即无限等待);

    最后删除对应的BlockQueue对象;

总体流程为生产者线程持续生成随机算术任务并将其加入共享队列(临界资源区)中,消费者线程从该队列取出任务并执行随后打印执行结果;

其中BlockQueue类负责处理线程同步,确保生产者和消费者能够安全的访问共享队列;

对应的执行结果为:

$ ./blockqueue 
The thread-448 push a task , 6 -  9 = ? 
The thread-152 handled a task ,  6 -  9 =  -3 , exit code : 0
------------------------------------
The thread-448 push a task , 2 /  8 = ? 
The thread-152 handled a task ,  2 /  8 =   0 , exit code : 0
------------------------------------
The thread-448 push a task , 6 +  8 = ? 
The thread-152 handled a task ,  6 +  8 =  14 , exit code : 0
------------------------------------
The thread-448 push a task , 3 %  1 = ? 
The thread-152 handled a task ,  3 %  1 =   0 , exit code : 0
------------------------------------
The thread-448 push a task , 5 +  9 = ? 
The thread-152 handled a task ,  5 +  9 =  14 , exit code : 0
------------------------------------
The thread-448 push a task , 7 /  8 = ? 
The thread-152 handled a task ,  7 /  8 =   0 , exit code : 0
------------------------------------
...
...
  • 多生产者多消费者

    当前BlockQueue生产者消费者模型支持多生产者多消费者的情况,对应只需将代码修改为:

    int main() {
      // 初始化随机数生成器
      srand(time(nullptr));
    
      // 创建一个 BlockQueue<Task> 对象作为共享队列
      BlockQueue<Task> *bq = new BlockQueue<Task>();
    
      // 创建消费者线程
      pthread_t c_tids[3], p_tids[3];
      for (int i = 0; i < 3; ++i) {
        pthread_create(c_tids + i, nullptr, Consumer, bq);
      }
      // 创建生产者线程
      for (int i = 0; i < 3; ++i) {
        pthread_create(p_tids + i, nullptr, Productor, bq);
      }
      // 等待线程结束(实际上是无限等待)
      for (int i = 0; i < 3; ++i) pthread_join(c_tids[i], nullptr);
      for (int i = 0; i < 3; ++i) pthread_join(p_tids[i], nullptr);
    
      // 清理资源(实际上不会执行到这里)
      delete bq;
      return 0;
    }
    

    该代码中创建了多个生产者与多个消费者线程,其中使用了c_tids[3]p_tids[3]数组来保存管理线程;

    其余代码可不变;

    对应的运行结果为(删除分隔符-------的打印):

    $ ./blockqueue 
    The thread-392 push a task , 3 +  2 = ? 
    The thread-208 handled a task ,  3 +  2 =   5 , exit code : 0
    The thread- 96 push a task , 8 +  7 = ? 
    The thread-688 push a task , 2 %  3 = ? 
    The thread-800 handled a task ,  8 +  7 =  15 , exit code : 0
    The thread-504 handled a task ,  2 %  3 =   2 , exit code : 0
    The thread-392 push a task , 3 %  3 = ? 
    The thread-504 handled a task ,  3 %  3 =   0 , exit code : 0
    The thread-688 push a task , 4 -  8 = ? 
    The thread-208 handled a task ,  0 -  9 =  -9 , exit code : 0
    The thread- 96 push a task , 0 -  9 = ? 
    The thread-800 handled a task ,  4 -  8 =  -4 , exit code : 0
    The thread-392 push a task , 6 %  4 = ? 
    The thread-208 handled a task ,  6 *  5 =  30 , exit code : 0
    The thread- 96 push a task , 6 %  8 = ? 
    The thread-688 push a task , 6 *  5 = ? 
    The thread-504 handled a task ,  6 %  4 =   2 , exit code : 0
    The thread-800 handled a task ,  6 %  8 =   6 , exit code : 0
    ...
    ...
    

    可在运行前在消费者函数或是生产者函数利用usleep()sleep()控制其生产者与消费者的生产消费速率;

    该模型生产者和消费者的生产消费速率不需严格控制,其对端将自行同步;


参考代码

请添加图片描述

(供参考) CSDN - Dio夹心小面包 / Gitee - 半介莽夫

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

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

相关文章

多级指针的使用

文章目录 &#x1f34a;自我介绍&#x1f34a;指针的设计规则&#x1f34a;多级指针的结论&#x1f34a;二级指针输出数组中的某个值的表示方法 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以&#xff1a;点赞关注评论收藏&#xff08;一键四连&#xff09;哦~ &…

Effective Java 中文版(第2版 电子版教程)

前言 Java从诞生到日趋完善&#xff0c;经过了不断的发展壮大&#xff0c;目前全世界拥有了成千上万的Java开发人员。如何编写出更清晰、更正确、更健壮且更易于重用的代码&#xff0c;是大家所追求的目标之一。本书的作者JoshuaBloch曾经是Sun公司的杰出工程师&#xff0c;带…

vite+typescript项目 报错:找不到模块“./*.vue”或其相应的类型声明——解决方案

declare module *.vue {import type { DefineComponent } from vueconst vueComponent: DefineComponent<{}, {}, any>export default vueComponent }

fastjson-1.2.24利用

参考视频&#xff1a;fastjson反序列化漏洞2-1.2.24利用 参考博客&#xff1a;Fastjson系列二——1.2.22-1.2.24反序列化漏洞 分析版本 fastjson1.2.24 JDK 8u141 fastjson反序列化特点 不需要实现Serializable 因为对于找不到符合条件的反序列化器&#xff0c;就把类当作…

SAP ERP 通过SAP PO LDAP适配器与微软AD域服务系统集成案例

一、客户介绍 上海某芯片制造公司的主要产品应用于图像传感器、 图像信号处理芯片、 低功耗芯片、 射频芯片等。专注集成电路技术开发与制造&#xff0c;服务于图像传感器、图形图像信号处理芯片、低功耗芯片、射频芯片等领域的全球客户。 二、项目需求 该企业内部办公电…

Flutter 初识:数据表格和卡片

Flutter数据表格和卡片小结 Table属性解析示例TableRow属性解析 TableCell属性解析 DataTable属性解析示例DataColumn属性解析示例 DataRow属性解析示例 DataCell属性解析 Card属性解析示例 Table Table 是 Flutter 中用于显示表格布局的小部件。它允许你定义行和列&#xff0…

前端日历插件VCalendar

官网地址 API | VCalendar 1.安装 yarn add v-calendarnext popperjs/core 2.全局引入 mian.js //日历插件 import VCalendar from v-calendar; import v-calendar/style.css;app.use(VCalendar); 3.使用 <div><VCalendar reservationTime expanded borderless…

无涯问知AI PC版发布,星环科技开启个人大模型应用新篇章

5月30-31日&#xff0c;2024向星力未来数据技术峰会期间&#xff0c;星环科技推出无涯问知AI PC版&#xff0c;这是一款专为个人用户设计的大模型应用产品&#xff0c;标志着个人智能应用时代的全面展开。 无涯问知AI PC版基于星环科技先进的大模型技术&#xff0c;可以在配备英…

jenkins获取sonarqube质量门禁结果

前景 在使用 Jenkins 集成 SonarQube 时&#xff0c;获取质量门禁&#xff08;Quality Gate&#xff09;结果非常重要。SonarQube 的质量门禁是一种质量控制机制&#xff0c;用于评估代码质量是否符合预设的标准。以下是获取质量门禁结果的意义和作用&#xff1a; 评估代码质量…

跟张良均老师学大数据人工智能-批量集训营开班中

随着我国大数据和人工智能产业的飞速发展&#xff0c;未来社会对高素质科技人才的需求日益旺盛。为助力广大青少年提前掌握前沿技术&#xff0c;实现自我价值&#xff0c;泰迪智能科技多名优秀老师联合打造暑期大数据人工智能集训营&#xff0c;旨在培养具备创新精神和实战能力…

常见红外协议整理

1.NEC 1.1 信号编码 载波频率&#xff1a;38kHz载波&#xff0c;载波占空比建议位1/3或1/4。 逻辑"0":562.5μs的脉冲burst(约21个周期) 562.5μs的空闲,总时长1.125ms 逻辑"1":562.5μs的脉冲burst(约21个周期) 1.6875ms的空闲,总时长2.25ms 引导…

大模型的一些思考

迄今为止&#xff0c;应该没有人还怀疑大模型的能力吧&#xff1f;但目前大模型实现真正落地&#xff0c;其实还有一段艰难的路要走。 和大模型相关的一些术语 **1. 大模型&#xff1a;**一般指1亿以上参数的模型&#xff0c;但是这个标准一直在升级&#xff0c;目前万亿参数…

树模型详解3-xgboost

回归树&#xff1a; 表达式为T&#xff08;x&#xff09;wq&#xff08;x&#xff09;&#xff0c;意思为&#xff1a;一个样本x&#xff0c;落到树的哪个叶子结点由q&#xff08;x&#xff09;得出&#xff0c;具体叶子结点对应的值由w函数得出 如何构建函数&#xff1a; 运…

智能视频监控在车辆管理与安全预警中的应用挑战

随着公共交通的客货运事业蓬勃发展&#xff0c;车辆保有量逐年增加&#xff0c;交通安全管理形势愈发严峻。为应对超速、疲劳驾驶、两客一危等违规驾驶行为&#xff0c;智能视频监控技术成为提升交通安全管理水平的关键手段。然而&#xff0c;尽管智能视频监控技术在理论上具有…

MATLAB(9)GIS模型

一、介绍 在GIS&#xff08;地理信息系统&#xff09;中&#xff0c;模型的实现可以非常多样化&#xff0c;取决于你想要解决的具体问题。MATLAB作为一个强大的数值计算和可视化工具&#xff0c;可以被用来开发GIS相关的模型&#xff0c;尽管它不是专门为GIS设计的&#xff08…

免费【2024】springboot 大学生二手电子产品交易平台设计与实现

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

Python机器学习实战:分类算法之支持向量机-垃圾邮件识别

为了解决特定问题而进行的学习是提高效率的最佳途径。这种方法能够使我们专注于最相关的知识和技能&#xff0c;从而更快地掌握解决问题所需的能力。 目录 支持向量机算法介绍 练习题 Python代码与分析 支持向量机和朴素贝叶斯的联系 支持向量机算法介绍 支持向量机&#…

时尚好看的APPUI设计离不开社交属性

时尚好看的APP UI设计离不开社交属性 艾斯视觉作为ui设计和前端开发从业者&#xff0c;其观点始终认为&#xff1a;移动应用程序&#xff08;APP&#xff09;已成为人们日常生活中不可或缺的一部分。一个时尚好看的APP UI设计不仅能够吸引用户&#xff0c;更能提升用户体验。而…

产品经理必备:8大高效的需求跟踪工具

本文将分享8款主流需求进度跟踪工具&#xff1a;PingCode、Worktile、Teambition、禅道、Tapd、Trello、Wrike、Monday.com。 每个产品经理都面临着如何有效监控项目进展、确保团队与目标同步的问题。选择合适的工具不仅可以减少误会和延误&#xff0c;还能显著提升团队的生产效…

【第六节】python的特殊语法和常用模块

目录 一、特殊语法 1.1 高阶函数 1.2 函数嵌套 1.3 装饰器 二、常用的模块 2.1 time模块 2.1.1 时间的表示方式 2.1.2 time 的使用方式 2.2 datetime 模块 2.2.1 datetime 模块总览 2.2.2 date 类 2.2.3 time 类 2.2.4 datetime 类 2.2.5 timedelta 类 2.3 rand…