【Linux】29.Linux 多线程(3)

文章目录

    • 8.4 生产者消费者模型
      • 8.4.1 为何要使用生产者消费者模型
      • 8.4.2 生产者消费者模型优点
    • 8.5 基于BlockingQueue的生产者消费者模型
      • 8.5.1 C++ queue模拟阻塞队列的生产消费模型
    • 8.6. 为什么pthread_cond_wait 需要互斥量?
    • 8.7 条件变量使用规范
    • 8.8 条件变量的封装
    • 8.9 POSIX信号量
      • 8.9.1 基于环形队列的生产消费模型
        • 实现一个信号量的封装
        • 实现一个线程安全的环形队列


8.4 生产者消费者模型

8.4.1 为何要使用生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。


8.4.2 生产者消费者模型优点

  • 解耦
  • 支持并发
  • 支持忙闲不均

62ea71f2aec93f2e52ffb13d09048f15


8.5 基于BlockingQueue的生产者消费者模型

在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)

04ac912c03dc54bfd7e721a52b877e29


8.5.1 C++ queue模拟阻塞队列的生产消费模型

单生产者,单消费者

代码:

#ifndef __BLOCK_QUEUE_HPP__  // 头文件防重包含宏
#define __BLOCK_QUEUE_HPP__#include <iostream>
#include <queue>
#include <pthread.h>  // POSIX线程库头文件// 阻塞队列类模板,实现单生产者-单消费者模型
template <typename T>
class BlockQueue
{
private:// 检查队列是否已满bool IsFull(){return _block_queue.size() == _cap;}// 检查队列是否为空bool IsEmpty(){return _block_queue.empty();}public:// 构造函数:初始化队列容量和同步原语BlockQueue(int cap) : _cap(cap){// 单生产者-单消费者模型不需要记录等待数量pthread_mutex_init(&_mutex, nullptr);          // 初始化互斥锁pthread_cond_init(&_product_cond, nullptr);    // 初始化生产者条件变量pthread_cond_init(&_consum_cond, nullptr);     // 初始化消费者条件变量}// 生产者接口 - 仅供单个生产者线程调用// 参数in: 要入队的元素void Push(const T& in){pthread_mutex_lock(&_mutex);    // 获取互斥锁,进入临界区// 如果队列满,生产者需要等待// 单生产者场景使用if而不是while,因为不会有虚假唤醒if(IsFull()){// pthread_cond_wait会:// 1. 释放互斥锁// 2. 阻塞等待条件变量// 3. 被唤醒后重新获取互斥锁pthread_cond_wait(&_product_cond, &_mutex);}// 将数据放入队列_block_queue.push(in);// 唤醒可能在等待的消费者// 单消费者场景下最多只有一个线程在等待pthread_cond_signal(&_consum_cond);pthread_mutex_unlock(&_mutex);  // 释放互斥锁,离开临界区}// 消费者接口 - 仅供单个消费者线程调用// 参数out: 用于存储出队元素的指针void Pop(T* out){pthread_mutex_lock(&_mutex);    // 获取互斥锁,进入临界区// 如果队列空,消费者需要等待// 单消费者场景使用if而不是while,因为不会有虚假唤醒if(IsEmpty()){pthread_cond_wait(&_consum_cond, &_mutex);}// 从队列取出数据*out = _block_queue.front();    // 获取队首元素_block_queue.pop();             // 移除队首元素// 唤醒可能在等待的生产者// 单生产者场景下最多只有一个线程在等待pthread_cond_signal(&_product_cond);pthread_mutex_unlock(&_mutex);   // 释放互斥锁,离开临界区}// 析构函数:清理同步原语~BlockQueue(){pthread_mutex_destroy(&_mutex);           // 销毁互斥锁pthread_cond_destroy(&_product_cond);     // 销毁生产者条件变量pthread_cond_destroy(&_consum_cond);      // 销毁消费者条件变量}private:std::queue<T> _block_queue;        // 底层队列容器int _cap;                          // 队列最大容量pthread_mutex_t _mutex;            // 互斥锁,保护共享资源pthread_cond_t _product_cond;      // 生产者条件变量,用于生产者等待pthread_cond_t _consum_cond;       // 消费者条件变量,用于消费者等待
};#endif

多生产者,多消费者

代码:

#ifndef __BLOCK_QUEUE_HPP__
#define __BLOCK_QUEUE_HPP__#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>// 阻塞队列类模板,用于实现生产者-消费者模型
template <typename T>
class BlockQueue
{
private:// 检查队列是否已满bool IsFull(){return _block_queue.size() == _cap;}// 检查队列是否为空bool IsEmpty(){return _block_queue.empty();}public:// 构造函数:初始化队列容量和同步原语BlockQueue(int cap) : _cap(cap){_productor_wait_num = 0;  // 等待的生产者数量_consumer_wait_num = 0;   // 等待的消费者数量pthread_mutex_init(&_mutex, nullptr);           // 初始化互斥锁pthread_cond_init(&_product_cond, nullptr);     // 初始化生产者条件变量pthread_cond_init(&_consum_cond, nullptr);      // 初始化消费者条件变量}// 入队方法:生产者接口void Enqueue(T &in){pthread_mutex_lock(&_mutex);  // 获取互斥锁,进入临界区// 当队列满时,生产者需要等待while(IsFull())  // 使用while而不是if,防止虚假唤醒{// 生产者等待流程:// 1. 增加等待计数// 2. 释放互斥锁并等待条件变量// 3. 被唤醒后减少等待计数_productor_wait_num++;pthread_cond_wait(&_product_cond, &_mutex);_productor_wait_num--;}// 将数据放入队列_block_queue.push(in);// 如果有消费者在等待,唤醒其中一个if(_consumer_wait_num > 0)pthread_cond_signal(&_consum_cond);  // 也可以用broadcast唤醒所有pthread_mutex_unlock(&_mutex);  // 释放互斥锁}// 出队方法:消费者接口void Pop(T *out){pthread_mutex_lock(&_mutex);  // 获取互斥锁,进入临界区// 当队列空时,消费者需要等待while(IsEmpty())  // 使用while防止虚假唤醒{// 消费者等待流程:// 1. 增加等待计数// 2. 释放互斥锁并等待条件变量// 3. 被唤醒后减少等待计数_consumer_wait_num++;pthread_cond_wait(&_consum_cond, &_mutex);_consumer_wait_num--;}// 从队列取出数据*out = _block_queue.front();_block_queue.pop();// 如果有生产者在等待,唤醒其中一个if(_productor_wait_num > 0)pthread_cond_signal(&_product_cond);pthread_mutex_unlock(&_mutex);  // 释放互斥锁}// 析构函数:清理同步原语~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_product_cond);pthread_cond_destroy(&_consum_cond);}private:std::queue<T> _block_queue;        // 底层队列容器int _cap;                          // 队列容量pthread_mutex_t _mutex;            // 互斥锁,保护共享资源pthread_cond_t _product_cond;      // 生产者条件变量pthread_cond_t _consum_cond;       // 消费者条件变量int _productor_wait_num;           // 等待的生产者数量int _consumer_wait_num;            // 等待的消费者数量
};#endif

这里采用模版,是想告诉我们,队列中不仅仅可以放置内置类型,比如int, 对象也可以作为任务来参与生产消费的过程。


8.6. 为什么pthread_cond_wait 需要互斥量?

  • 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
  • 条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。

4c49e3814e454e94ca26513d160414ac

按照上面的说法,我们设计出如下的代码:先上锁,发现条件不满足,解锁,然后等待在条件变量上不就行了?如下代码:

// 错误的设计
pthread_mutex_lock(&mutex);
while (condition_is_false) {pthread_mutex_unlock(&mutex);//解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过pthread_cond_wait(&cond);pthread_mutex_lock(&mutex);
}
pthread_mutex_unlock(&mutex);
  • 由于解锁和等待不是原子操作。调用解锁之后, pthread_cond_wait 之前,如果已经有其他线程获取到互斥量,摒弃条件满足,发送了信号,那么 pthread_cond_wait 将错过这个信号,可能会导致线程永远阻塞在这个 pthread_cond_wait 。所以解锁和等待必须是一个原子操作。
  • int pthread_cond_wait(pthread_cond_ t *cond,pthread_mutex_ t * mutex); 进入该函数后,会去看条件量等于0不?等于,就把互斥量变成1,直到cond_ wait返回,把条件量改成1,把互斥量恢复成原样。

8.7 条件变量使用规范

等待条件代码

pthread_mutex_lock(&mutex);
while (条件为假)pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);

给条件发送信号代码

pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);

8.8 条件变量的封装

#pragma once  // 防止头文件重复包含,比#ifndef更现代的方式#include <iostream>
#include <string>
#include <pthread.h>
#include "Lock.hpp"  // 包含互斥锁的封装类// 条件变量模块命名空间
namespace CondModule
{// 使用互斥锁模块的命名空间using namespace LockModule;// 条件变量封装类// 目的:将pthread_cond_t的C接口封装为C++类,实现RAII机制class Cond{public:// 构造函数:初始化条件变量Cond(){// pthread_cond_init返回0表示成功,非0表示失败int n = pthread_cond_init(&_cond, nullptr);  // 默认属性初始化(void)n; // 暂时忽略返回值,实际使用时应该添加错误处理和日志}// 等待条件变量// 参数:互斥锁的引用,必须在调用Wait前已经获得锁void Wait(Mutex &mutex){// pthread_cond_wait会自动:// 1. 释放互斥锁// 2. 等待条件// 3. 被唤醒后重新获取锁int n = pthread_cond_wait(&_cond, mutex.GetMutexOriginal());(void)n;  // 暂时忽略返回值,实际使用时应该添加错误处理}// 唤醒一个等待的线程void Notify(){// 如果有多个线程在等待,则随机唤醒其中一个int n = pthread_cond_signal(&_cond);(void)n;  // 暂时忽略返回值}// 唤醒所有等待的线程void NotifyAll(){// 唤醒所有等待该条件变量的线程// 被唤醒的线程仍需要重新获得互斥锁才能继续执行int n = pthread_cond_broadcast(&_cond);(void)n;  // 暂时忽略返回值}// 析构函数:销毁条件变量~Cond(){// 销毁条件变量,释放相关资源int n = pthread_cond_destroy(&_cond);(void)n; // 暂时忽略返回值,实际使用时应该添加错误处理和日志}private:pthread_cond_t _cond;  // 底层条件变量// 禁止复制和赋值// C++11前的方式:声明为private但不实现Cond(const Cond&);Cond& operator=(const Cond&);};
}

为了让条件变量更具有通用性,建议封装的时候,不要在Cond类内部引用对应的封装互斥量,要不然后面组合的时候,会因为代码耦合的问题难以初始化,因为一般而言Mutex和Cond基本是一起创建的。


8.9 POSIX信号量

POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。但POSIX可以用于线程间同步。

信号量的本质是一把计数器,那么这把计数器的本质是什么?

是用来描述资源数目的,把资源是否就绪放在了临界区之外。申请信号量时,其实就间接的已经在做判断了。

初始化信号量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:pshared:0表示线程间共享,非零表示进程间共享value:信号量初始值

销毁信号量

int sem_destroy(sem_t *sem);

等待信号量

功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()

发布信号量

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1int sem_post(sem_t *sem);//V()

上一节生产者-消费者的例子是基于queue的,其空间可以动态分配,现在基于固定大小的环形队列重写这个程序(POSIX信号量):


8.9.1 基于环形队列的生产消费模型

环形队列采用数组模拟,用模运算来模拟环状特性

dd7272e89d88db62fda8f13f4583e89c

环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态。

ccb0345294ea86e1e81ef9b379abe8b2

但是我们现在有信号量这个计数器,就很简单的进行多线程间的同步过程。


实现一个信号量的封装
#pragma once  // 防止头文件重复包含#include <iostream>
#include <semaphore.h>  // POSIX信号量头文件// 信号量封装类
// 将POSIX信号量封装为C++类,实现RAII机制
// P-V操作源自荷兰语Proberen(尝试)/Verhogen(增加)
class Sem
{
public:// 构造函数:初始化信号量// 参数n: 信号量初始值,表示可用资源数量Sem(int n){// sem_init参数说明:// &_sem: 信号量对象// 0: 信号量类型,0表示线程间共享,非0表示进程间共享// n: 信号量初始值sem_init(&_sem, 0, n);}// P操作(等待操作)// 使信号量值减1,如果信号量值为0则阻塞// 对应生产者-消费者模型中的取走资源操作void P(){// sem_wait会导致:// 1. 信号量值大于0:将其减1并继续执行// 2. 信号量值等于0:阻塞等待,直到信号量大于0sem_wait(&_sem);}// V操作(释放操作)// 使信号量值加1,如果有线程阻塞则唤醒一个// 对应生产者-消费者模型中的放入资源操作void V(){// sem_post会:// 1. 将信号量值加1// 2. 如果有线程因为sem_wait阻塞,则唤醒其中一个sem_post(&_sem);}// 析构函数:销毁信号量~Sem(){// 释放信号量相关的资源sem_destroy(&_sem);}private:sem_t _sem;  // POSIX信号量对象// 禁止拷贝构造和赋值操作Sem(const Sem&) = delete;Sem& operator=(const Sem&) = delete;
};
  1. 核心功能

    • 实现了一个计数器,用于控制对共享资源的访问

    • 比如:有3个座位的餐厅,这个信号量初始值就设为3

  2. 主要操作

  • P() 操作(等待):
    • 想进入餐厅时调用
    • 如果还有座位(信号量>0),就能直接进入,计数器-1
    • 如果没座位(信号量=0),就要在门口等待
  • V() 操作(释放):
    • 离开餐厅时调用
    • 释放一个座位,计数器+1
    • 如果有人在等待,会唤醒一个等待的人

实现一个线程安全的环形队列
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <semaphore.h>
#include <pthread.h>// 环形队列实现 - 支持多生产者多消费者
// 设计思路 "321":
// 3: 需要处理三种同步关系
//    a) 生产者和消费者之间的互斥与同步
//    b) 多个生产者之间的互斥
//    c) 多个消费者之间的互斥
// 2: 使用两把互斥锁
//    - 一把用于生产者间互斥
//    - 一把用于消费者间互斥
// 1: 一个循环队列作为数据缓冲区template<typename T>
class RingQueue
{
private:// 辅助函数:加锁void Lock(pthread_mutex_t &mutex){pthread_mutex_lock(&mutex);}// 辅助函数:解锁void Unlock(pthread_mutex_t &mutex){pthread_mutex_unlock(&mutex);}public:// 构造函数:初始化环形队列和同步原语// 参数cap: 环形队列容量RingQueue(int cap): _ring_queue(cap),        // 初始化vector大小为cap_cap(cap),                 // 保存容量_room_sem(cap),            // 空闲位置信号量,初值为容量_data_sem(0),              // 数据项信号量,初值为0_productor_step(0),        // 生产位置,从0开始_consumer_step(0)          // 消费位置,从0开始{// 初始化生产者和消费者互斥锁pthread_mutex_init(&_productor_mutex, nullptr);pthread_mutex_init(&_consumer_mutex, nullptr);}// 入队方法 - 生产者调用// 参数in: 要入队的数据void Enqueue(const T &in){_room_sem.P();    // 等待空闲位置Lock(_productor_mutex);    // 生产者间互斥// 此时一定有空间可用(由信号量保证)_ring_queue[_productor_step++] = in;  // 放入数据_productor_step %= _cap;              // 循环更新位置Unlock(_productor_mutex);   // 解除生产者间互斥_data_sem.V();    // 通知有新数据可用}// 出队方法 - 消费者调用// 参数out: 存储出队数据的指针void Pop(T *out){_data_sem.P();    // 等待有数据可用Lock(_consumer_mutex);    // 消费者间互斥*out = _ring_queue[_consumer_step++];  // 取出数据_consumer_step %= _cap;                // 循环更新位置Unlock(_consumer_mutex);   // 解除消费者间互斥_room_sem.V();    // 通知有新空位可用}// 析构函数:清理同步原语~RingQueue(){pthread_mutex_destroy(&_productor_mutex);pthread_mutex_destroy(&_consumer_mutex);}private:// 1. 环形队列存储结构std::vector<T> _ring_queue;    // 存储数据的环形缓冲区int _cap;                      // 队列容量// 2. 生产和消费位置int _productor_step;           // 生产者放入位置int _consumer_step;            // 消费者取出位置// 3. 信号量 - 用于生产者消费者同步Sem _room_sem;                 // 空闲位置信号量,控制生产者Sem _data_sem;                 // 数据项信号量,控制消费者// 4. 互斥锁 - 用于多生产者/消费者间互斥pthread_mutex_t _productor_mutex;  // 生产者间互斥pthread_mutex_t _consumer_mutex;   // 消费者间互斥
};

用一个餐厅自助取餐区的例子来解释:

  1. 基本结构

    • 想象一个传送带上有固定数量(cap)的餐盘位置

    • _ring_queue就是这个传送带

    • _productor_step是厨师放餐盘的位置

    • _consumer_step是顾客取餐盘的位置

  2. 核心功能

  • Enqueue(): 厨师(生产者)放餐
    • 检查是否有空位置(_room_sem)
    • 确保只有一个厨师在放餐(_productor_mutex)
    • 放入餐品
    • 通知有新餐品可取(_data_sem)
  • Pop(): 顾客(消费者)取餐
    • 检查是否有餐品可取(_data_sem)
    • 确保只有一个顾客在取餐(_consumer_mutex)
    • 取走餐品
    • 通知有新空位可用(_room_sem)
  1. 同步机制

    • 使用两个信号量控制生产和消费

    • 使用两个互斥锁确保多个生产者/消费者之间不冲突

  2. 环形特性

    • 位置到达末尾后回到开头继续使用(像传送带一样循环)

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【漫话机器学习系列】084.偏差和方差的权衡(Bias-Variance Tradeoff)

偏差和方差的权衡&#xff08;Bias-Variance Tradeoff&#xff09; 1. 引言 在机器学习模型的训练过程中&#xff0c;我们常常面临一个重要的挑战&#xff1a;如何平衡 偏差&#xff08;Bias&#xff09; 和 方差&#xff08;Variance&#xff09;&#xff0c;以提升模型的泛…

OpenCV:视频背景减除

目录 简述 1. MOG &#x1f537;1.1 主要特点 &#x1f537;1.2 代码示例 &#x1f537;1.3 运行效果 2. MOG2 &#x1f537;2.1 主要特点 &#x1f537;2.2 代码示例 &#x1f537;2.3 运行效果 3. KNN 4. GMG 5. CNT 6. LSBP 7. 如何选择适合的接口&#xff…

【SpringBoot篇】基于Redis分布式锁的 误删问题 和 原子性问题

文章目录 ??Redis的分布式锁??误删问题 ??解决方法??代码实现 ??原子性问题 ??Lua脚本 ?利用Java代码调用Lua脚本改造分布式锁??代码实现 ??Redis的分布式锁 Redis的分布式锁是通过利用Redis的原子操作和特性来实现的。在分布式环境中&#xff0c;多个应用…

计算机视觉语义分割——Attention U-Net(Learning Where to Look for the Pancreas)

计算机视觉语义分割——Attention U-Net(Learning Where to Look for the Pancreas) 文章目录 计算机视觉语义分割——Attention U-Net(Learning Where to Look for the Pancreas)摘要Abstract一、Attention U-Net1. 基本思想2. Attention Gate模块3. 软注意力与硬注意力4. 实验…

Unity笔试常考

线程同步的几种方式 1.信号量pv操作 2.互斥加锁 3.条件变量 五层网络协议指的是哪五层 1.应用层 2.运输层 3.网络层 4.链路层 5.物理层 TCP和UDP区别 tcp 面向连接&#xff0c;保证发送顺序&#xff0c;速度慢&#xff0c;必须在线&#xff0c;三次握手&#xff0c;4次挥手…

Docker数据卷管理及优化

一、基础概念 1.docker数据卷是一个可供容器使用的特殊目录&#xff0c;它绕过了容器的文件系统&#xff0c;直接将数据存在宿主机上。 2.docker数据卷的作用&#xff1a; 数据持久化&#xff1a;即使容器被删除或重建数据卷中的数据仍然存在 数据共享&#xff1a;多个容器可以…

【AIGC】冷启动数据与多阶段训练在 DeepSeek 中的作用

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;冷启动数据的作用冷启动数据设计 &#x1f4af;多阶段训练的作用阶段 1&#xff1a;冷启动微调阶段 2&#xff1a;推理导向强化学习&#xff08;RL&#xff0…

Qt:项目文件解析

目录 QWidget基础项目文件解析 .pro文件解析 widget.h文件解析 widget.cpp文件解析 widget.ui文件解析 main.cpp文件解析 认识对象模型 窗口坐标系 QWidget基础项目文件解析 .pro文件解析 工程新建好之后&#xff0c;在工程目录列表中有⼀个后缀为 ".pro" …

BiGRU双向门控循环单元多变量多步预测,光伏功率预测(Matlab完整源码和数据)

代码地址&#xff1a;BiGRU双向门控循环单元多变量多步预测&#xff0c;光伏功率预测&#xff08;Matlab完整源码和数据) BiGRU双向门控循环单元多变量多步预测&#xff0c;光伏功率预测 一、引言 1.1、研究背景和意义 随着全球对可再生能源需求的不断增长&#xff0c;光伏…

数据结构与算法-递归

单路递归 二分查找 /*** 主函数&#xff1a;执行二分查找。* * param a 要搜索的数组&#xff08;必须是已排序的&#xff09;* param target 目标值* return 返回目标值在数组中的索引&#xff1b;如果未找到&#xff0c;则返回 -1*/ public static int binarySearch(int[] …

【STM32F1】一种使用通用定时器实现各个通道独立输出不同指定数量脉冲的方法

一种使用通用定时器实现独立通道输出指定数量脉冲的方法 一种使用通用定时器实现独立通道输出指定数量脉冲的方法概述实验平台配置步骤1. 初始化定时器与GPIO2. 设置定时器工作模式3. 编写脉冲计数逻辑4. 调整参数以满足要求注意事项 代码实现电机结构体配置&#xff0c;GPIO配…

【Java基础】序列化、反序列化和不可变类

Hi~&#xff01;这里是奋斗的明志&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f331;&#x1f331;个人主页&#xff1a;奋斗的明志 &#x1f331;&#x1f331;所属专栏&#xff1a;Java基础面经 &#x1f4da;本系列文章为个…

android apk反编译

使用解压工具解压apk&#xff0c;使用dex2jar解析其中的dex文件为jar文件&#xff0c;再使用jd-gui将class反编译为java文件 1 工具下载 dex2jar下载 https://github.com/pxb1988/dex2jar/releases 直接去github上下载最新发布版本 注意&#xff0c;如果后面使用过程中遇到No…

PAT乙级( 1009 说反话 1010 一元多项式求导)C语言版本超详细解析

1009 说反话 给定一句英语&#xff0c;要求你编写程序&#xff0c;将句中所有单词的顺序颠倒输出。 输入格式&#xff1a; 测试输入包含一个测试用例&#xff0c;在一行内给出总长度不超过 80的字符串。字符串由若干单词和若干空格组成&#xff0c;其中单词是由英文字母&#x…

C++20导出模块及使用

1.模块声明 .ixx文件为导入模块文件 math_operations.ixx export module math_operations;//模块导出 //导出命名空间 export namespace math_ {//导出命名空间中函数int add(int a, int b);int sub(int a, int b);int mul(int a, int b);int div(int a, int b); } .cppm文件…

鸿蒙接入支付宝SDK后模拟器无法运行,报错error: install parse native so failed.

鸿蒙项目接入支付宝后&#xff0c;运行提示error: install parse native so failed. 该问题可能由于设备支持的 Abi 类型与 C 工程中的不匹配导致. 官网error: install parse native so failed.错误解决办法 根据官网提示在模块build-profile.json5中添加“x86_64”依然报错 问…

MongoDB开发规范

分级名称定义P0核心系统需7*24不间断运行&#xff0c;一旦发生不可用&#xff0c;会直接影响核心业务的连续性&#xff0c;或影响公司名誉、品牌、集团战略、营销计划等&#xff0c;可能会造成P0-P2级事故发生。P1次核心系统这些系统降级或不可用&#xff0c;会间接影响用户使用…

nodejs - vue 视频切片上传,本地正常,线上环境导致磁盘爆满bug

nodejs 视频切片上传&#xff0c;本地正常&#xff0c;线上环境导致磁盘爆满bug 原因&#xff1a; 然后在每隔一分钟执行du -sh ls &#xff0c;发现文件变得越来越大&#xff0c;即文件下的mp4文件越来越大 最后导致磁盘直接爆满 排查原因 1、尝试将m3u8文件夹下的所有视…

微信小程序如何使用decimal计算金额

第三方库地址&#xff1a;GitHub - MikeMcl/decimal.js: An arbitrary-precision Decimal type for JavaScript 之前都是api接口走后端计算&#xff0c;偶尔发现这个库也不错&#xff0c;计算简单&#xff0c;目前发现比较准确 上代码 导入js import Decimal from ../../uti…

unity学习29:摄像机camera相关skybox 和 Render Texture测试效果

目录 1 摄像机 1.1 每个Scene里都自带一个摄像机 camera 1.2 可以创建多个camera 1.3 下面先看backgroundtype: 2 backgroundtype: 天空盒 skybox 2.1 清除标志,清除&#xff1a;天空盒 自选天空盒 2.2 window /Asset Store 2.3 导入skybox 3 backgroundtype: 纯色…