C++ 基于多设计模式下的同步异步⽇志系统-1准备工作

news2025/4/20 14:14:49

一.项目介绍

项⽬介绍
本项⽬主要实现⼀个⽇志系统, 其主要⽀持以下功能:
• ⽀持多级别⽇志消息
• ⽀持同步⽇志和异步⽇志
• ⽀持可靠写⼊⽇志到控制台、⽂件以及滚动⽂件中
• ⽀持多线程程序并发写⽇志
• ⽀持扩展不同的⽇志落地⽬标地

二.日志系统的三种实现方式

实现方式原理简述优点缺点适用场景
1. 控制台输出 (printf/std::cout)直接在控制台输出日志信息,不进行落地文件记录简单、直观、便于开发调试无法记录历史日志、对线上调试不适用本地开发调试、单线程程序
2. 同步写日志在当前业务线程中执行日志格式化 + 写入文件操作,每条日志调用都同步 write()实现简单、数据可靠每条日志都阻塞主流程,尤其在高并发下 write IO 成为性能瓶颈简单后端系统、低并发写日志场景
3. 异步写日志主线程仅负责将日志写入缓冲区,由专门线程写日志到文件高性能、非阻塞、不影响业务流程,适合高并发实现复杂、涉及线程、锁、双缓冲,落地时间略有延迟高性能服务、后台系统、分布式

三.相关技术知识补充

1 不定参宏函数

#include <iostream>
#include <cstdarg>
#define LOG(fmt, ...) printf("[%s:%d] " fmt "\n", __FILE__, __LINE__,##__VA_ARGS__)
int main()
{
    LOG("%s-%s", "hello", "wws);
    return 0;
}

之前rpc项目介绍过。

解释 ##__VA_ARGS__

__VA_ARGS__ 是 C 语言宏的可变参数,它允许宏接受不定数量的参数。
## 用于处理 "参数为空" 的情况,它的作用是:
如果 __VA_ARGS__ 为空,就去掉前面的 ' , ',防止格式错误。
如果 __VA_ARGS__ 有内容,它会正常展开。

2.C⻛格不定参函数

#include <iostream>
#include <cstdarg>
void printNum(int n, ...)
{
    va_list al;
    va_start(al, n); // 让al指向n参数之后的第⼀个可变参数
    for (int i = 0; i < n; i++)
    {
        int num = va_arg(al, int); // 从可变参数中取出⼀个整形参数
        std::cout << num << std::endl;
    }
    va_end(al); // 清空可变参数列表--其实是将al置空
}
int main()
{
    printNum(3, 11, 22, 33);
    printNum(5, 44, 55, 66, 77, 88);
    return 0;
}

printNum(int n, ...)的作用就是打印n个整型

1. va_list al;

定义一个变参处理变量 va_list 它是 C 语言提供的一个宏(实际上是一个结构体指针类型),专门用来处理 ... 这些变长参数。你可以把它理解为:一个“变参读取器”指针。

2.va_start(al, n);

初始化变参指针(定位起点)它告诉 al“变长参数”是从 n之后开始的,且后面参数的个数为n。(C 语言没有反射或参数数量的机制,编译器也不会告诉你 ... 有几个参数。必须通过最后一个确定参数的地址来推断后面变参的起始地址,这就是 va_start 的原理)

3.va_arg(al, int);

使用 va_arg() 逐个读取参数(大小为第二个类型的大小)。从 al 指向的地方读取一个 int 类型的值,并且把 al 自动向后移动。

4. va_end(ap);

清理资源(让 ap 无效)清空指针

#include <iostream>
#include <cstdarg>
void myprintf(const char *fmt, ...)
{
    // int vasprintf(char **strp, const char *fmt, va_list ap);
    char *res;
    va_list al;
    va_start(al, fmt);
    int len = vasprintf(&res, fmt, al);
    va_end(al);
    std::cout << res << std::endl;
    free(res);
}
int main()
{
    myprintf("%s-%d", "⼩明", 18);
    return 0;
}

你写了一个叫 myprintf 的函数,能像 printf 一样,接收格式字符串和多个参数,把结果 格式化成字符串,并通过 std::cout 打印出来。

fmt 是格式字符串:"%s-%d"

va_start(al, fmt); 告诉 al:从参数 fmt 后面的地方开始读取变参("小明", 18)。

vasprintf(&res, fmt, al);

这个函数做了三件事:

1.根据 fmt 和 al,拼出格式化后的字符串

2.自动调用 malloc 分配内存,存放结果字符串;

3.把 res 设为这个字符串的地址。

free(res);

因为 vasprintf 分配了堆内存,你必须用 free 手动释放,否则会内存泄漏。

3.C++⻛格不定参函数


#include <iostream>
void xprintf()
{
    std::cout << std::endl;
}
template <typename T, typename... Args>
void xprintf(const T &value, Args &&...args)
{
    std::cout << value << " ";
    if ((sizeof...(args)) > 0)
    {
        xprintf(std::forward<Args>(args)...);
    }
    else
    {
        xprintf();
    }
}
int main()
{
    xprintf("wws");
    xprintf("wws", 666);
    xprintf("wws", "0721", 666);
    return 0;
}

参数说明:

  • T:当前要处理的第一个参数

  • Args...:剩下的变长参数包

行为流程:

  1. 打印当前的 value

  2. 如果还有参数(sizeof...(args) > 0)就递归调用 xprintf(...)

  3. 否则,调用 xprintf()(终点),输出一个换行

  • ... 在前面:定义一个 参数包

  • ... 在后面:展开一个 参数包

(std::forward<Args>(args)...) 完美转发剩余的参数,右值传递完还是右值,左值还是左值。

为什么要写xprintf()参数为空的特化函数?

模板会一直展开直到参数为空

模板的递归展开并不会因为你进入 else 分支而立即停止递归。递归停止是通过“没有更多参数”来控制的。关键点是 你在调用 xprintf() 时,会不断把剩余的参数传递给下一个递归调用,直到参数包为空。

所以执行else后,并不会结束,还会继续递归直到参数包为空,所以必须写参数包为空的特化函数。

四.设计模式

1.六大设计原则

1.单一职责原则(SRP)

定义:一个类只负责一项职责。

应用:

  • Logger 只负责组织和发起日志输出。

  • Formatter 专注于格式化日志内容。

  • Sink 专注于日志“落地”(文件/控制台等输出方式)。

  • LogMsg 专注于日志数据结构封装。


🔓 2. 开闭原则(OCP)

定义:对扩展开放,对修改关闭。

应用:

  • 增加新的日志输出格式、日志落地方式(如新增 TCP 日志输出)→ 新增类即可,无需改动原逻辑。

  • 格式化模块通过解析 %d %m %t 等 pattern 字符串,支持灵活扩展。


🔁 3. 里氏替换原则(LSP)

定义:子类对象可以替代父类对象使用。

应用:

  • 所有日志输出类继承自抽象类 LogSink,只要实现 log() 方法,就能无缝替换。

  • SyncLogger / AsyncLogger 都继承自 Logger,任何需要 Logger 的地方都可以使用这两个实现。


🔌 4. 依赖倒置原则(DIP)

定义:高层模块不应该依赖底层模块,二者都应该依赖抽象。

应用:

  • 所有 Sink 都通过 LogSink::ptr 操作,具体使用的是哪个子类并不关心。

  • 日志器的创建通过 Builder 构建,调用方不直接依赖 Logger 实现类。


🧼 5. 接口隔离原则(ISP)

定义:类不应依赖它不使用的方法。

应用:

  • Formatter::format() 只依赖 LogMsg 数据,不暴露额外无关的操作。

  • Logger 类的 debug/info/warn/... 分开封装,调用者按需使用。


🧍 6. 迪米特法则(LoD)

定义:只与直接朋友通信,降低耦合。

应用:

  • 日志器通过 Logger::Builder 封装所有配置细节,调用者无需了解 Sink/Formatter 等底层实现。

  • 管理器 loggerManager 提供统一接口 getLogger(),外部无需知道 Logger 的创建细节。


总结一句话:

“用抽象构建框架,用实现扩展细节”,整个日志系统正是依据这一原则,通过设计模式把每个模块解耦,提升了系统的灵活性与可扩展性。

2.单例模式

单例模式是一种常见的设计模式,它保证一个类只有一个实例,并提供一个全局访问点。单例模式有两种实现方式 饿汉模式和懒汉模式

饿汉模式

程序启动时就会创建⼀个唯⼀的实例对象。 因为单例对象已经确定, 所以⽐较适⽤于多
线程环境中, 多线程获取单例对象不需要加锁, 可以有效的避免资源竞争, 提⾼性能。

//1.饿汉模式
class Singleton
{
private:
    static Singleton _eton;//在类内进行声明
    Singleton()// 私有构造函数
    :_data(66)
    {
        std::cout<<"单例对象构造"<<std::endl;
    }
    ~Singleton() {} // 私有析构函数
    Singleton (const Singleton&)=delete;//禁止拷贝
    Singleton&operator=(const Singleton&)=delete;//禁止赋值
private:
    int _data;
public:
    static Singleton& getInstance()
    {
        return _eton;
    }
};
Singleton Singleton:: _eton;//类外定义

1.构造析构私有 拷贝赋值函数禁止delete且私有

2.类内声明 静态成员变量 类外定义(程序运行时自动实例化)

3.类中提供静态函数(不需要类对象就能调用),用来获取单例对象。

优点

  • 线程安全:由于单例对象在程序启动时就已经创建,多个线程在调用 getInstance() 时无需加锁,可以避免资源竞争,因此性能较高。

  • 简单:代码结构简单,容易理解和实现。

缺点

  • 提前创建:单例对象会在程序启动时就创建,即使在程序运行过程中并不需要这个实例,也会被创建,这可能导致不必要的资源浪费。

  • 不可延迟加载:如果创建单例对象的过程非常复杂或资源消耗很大,程序启动时就会受到影响。

适用场景

  • 适合在程序启动时就需要加载的资源,例如配置管理、日志系统等。

  • 适用于实例的创建比较轻量,或者实例的创建和销毁不会占用太多资源的场景。

懒汉模式

第⼀次使⽤要使⽤单例对象的时候创建实例对象。如果单例对象构造特别耗时或者耗费济
源(加载插件、加载⽹络资源等), 可以选择懒汉模式, 在第⼀次使⽤的时候才创建对象。

//2.懒汉模式
class Singleton
{
private:
    Singleton()// 私有构造函数
    :_data(66)
    {
        std::cout<<"单例对象构造"<<std::endl;
    }
    ~Singleton() {} // 私有析构函数
    Singleton (const Singleton&)=delete;//禁止拷贝
    Singleton&operator=(const Singleton&)=delete;//禁止赋值
private:
    int _data;
public:
    static Singleton& getInstance()
    {
        static Singleton _eton;//只有第一次调用时创建实例(C++11 此时线程安全不需要加锁)
        return _eton;
    }
};

1.构造析构私有 拷贝赋值函数禁止delete且私有

2.在获取单例对象时getInstance()内部创建单例对象(static对象只会初始化一次)

优点

  • 延迟创建:单例对象只有在真正需要时才会被创建,避免了不必要的资源浪费,适用于实例化过程耗时或消耗资源的情况。

  • 线程安全使用 C++11 的 static 关键字保证静态局部变量在多线程环境下的安全初始化。

缺点

  • 延迟加载开销:虽然避免了程序启动时的资源消耗,但在首次调用 getInstance() 时,会有一定的延迟开销。

  • 复杂度较高:相比饿汉模式,懒汉模式的实现稍微复杂一些,尤其是早期版本的 C++,静态局部变量的线程安全性没有保证,需要额外的锁机制。

适用场景

  • 适合实例化开销较大、资源消耗较多的单例对象,或者对象的创建是延迟的、条件不固定的情况。

特性饿汉模式懒汉模式
实例化时机程序启动时即创建实例第一次调用 getInstance() 时创建实例
线程安全默认线程安全静态局部变量保证线程安全(C++11后)
内存消耗启动时即创建,可能浪费资源只有在首次访问时才创建,节省内存资源
性能更高性能,无锁定和延迟初次调用有延迟,可能有少许性能开销
实现复杂度简单易实现稍复杂,涉及线程安全和延迟加载
适用场景启动时必须加载的对象,资源轻量对象创建耗时或资源消耗较大的情况

3.工厂模式

1.简单工厂模式

通过一个统一的工厂类,根据传入的参数判断创建哪种产品(对象)。

所有产品类的创建逻辑都集中在一个工厂类中。

客户端
  ↓
SimpleFactory::create("苹果") or "香蕉"
  ↓
返回具体产品(Apple / Banana)

只有一个工厂,根据传入类型的不同,来生产不同的对象。

class Fruit {
public:
    virtual void show() = 0;
};

class Apple : public Fruit {
public:
    void show() override { std::cout << "我是苹果\n"; }
};

class Banana : public Fruit {
public:
    void show() override { std::cout << "我是香蕉\n"; }
};

class FruitFactory {
public:
    static std::shared_ptr<Fruit> createFruit(const std::string &type) {
        if (type == "apple") return std::make_shared<Apple>();
        if (type == "banana") return std::make_shared<Banana>();
        return nullptr;
    }
};

✅ 优点:

  • 简单易懂、实现成本低。

  • 客户端不需要知道具体产品类名,只需要告诉工厂“我要什么”。

❌ 缺点:

  • 违反开闭原则:添加新产品必须修改工厂代码。

  • 工厂类过于臃肿,职责过重,易造成维护困难。

✅ 适用场景:

  • 产品种类较少,变动不频繁的小项目或初期开发阶段。

方法二:模板函数

template<typename T, typename... Args>
static std::shared_ptr<T> create(Args&&... args)
{
    return std::make_shared<T>(std::forward<Args>(args)...);
}

符合开闭原则

2.工厂方法模式

每个产品类对应一个具体工厂类。

抽象出一个工厂接口,具体工厂负责创建对应的产品。

客户端只需使用对应的工厂,不再传入类型参数。

客户端
  ↓
AppleFactory::create()        BananaFactory::create()
  ↓                             ↓
返回 Apple                    返回 Banana

有多个工厂,一个子类就对应一个工厂。

class Fruit {
public:
    virtual void show() = 0;
};

class Apple : public Fruit {
public:
    void show() override { std::cout << "我是苹果\n"; }
};

class Banana : public Fruit {
public:
    void show() override { std::cout << "我是香蕉\n"; }
};

class FruitFactory {
public:
    virtual std::shared_ptr<Fruit> createFruit() = 0;
};

class AppleFactory : public FruitFactory {
public:
    std::shared_ptr<Fruit> createFruit() override {
        return std::make_shared<Apple>();
    }
};

class BananaFactory : public FruitFactory {
public:
    std::shared_ptr<Fruit> createFruit() override {
        return std::make_shared<Banana>();
    }
};

✅ 优点:

  • 遵循开闭原则:新增产品只需新增产品类和工厂类,无需修改现有代码。

  • 更加符合“职责单一”的设计原则。

❌ 缺点:

  • 每新增一个产品都要新增一个工厂类,类数量增多。

  • 不适合产品种类太多的场景,维护成本较高。

✅ 适用场景:

  • 产品变化频繁,且对扩展性有要求的中大型项目。

3.抽象工厂模式

不再是创建“单一”产品,而是创建产品族(多个功能相关的产品对象)。

定义一组工厂接口,每个工厂可以创建多个类型的产品。

有多种物品,水果 动物... 里面还可以细分苹果 香蕉,狗 羊

每一种物品对应一个工厂,每个工厂中有具体对象生成函数

所有工厂都继承于一个抽象工厂。

抽象工厂(AbstractFactory)
       ↓
  ------------------------
  ↓                      ↓
水果工厂(FruitFactory)   动物工厂(AnimalFactory)
  ↓                      ↓
createApple()            createDog()
createBanana()           createSheep()
#include <iostream>
#include <memory>
#include <string>

// ==== 抽象产品 ====
class Fruit {
public:
    virtual void show() = 0;
    virtual ~Fruit() = default;
};

class Animal {
public:
    virtual void voice() = 0;
    virtual ~Animal() = default;
};

// ==== 具体产品 ====
class Apple : public Fruit {
public:
    void show() override {
        std::cout << "我是苹果🍎" << std::endl;
    }
};

class Banana : public Fruit {
public:
    void show() override {
        std::cout << "我是香蕉🍌" << std::endl;
    }
};

class Dog : public Animal {
public:
    void voice() override {
        std::cout << "汪汪汪🐶" << std::endl;
    }
};

class Sheep : public Animal {
public:
    void voice() override {
        std::cout << "咩咩咩🐑" << std::endl;
    }
};

// ==== 抽象工厂接口 ====
class AbstractFactory {
public:
    virtual ~AbstractFactory() = default;
};

// ==== 水果工厂接口 ====
class FruitFactory : public AbstractFactory {
public:
    virtual std::shared_ptr<Fruit> createApple() = 0;
    virtual std::shared_ptr<Fruit> createBanana() = 0;
};

// ==== 动物工厂接口 ====
class AnimalFactory : public AbstractFactory {
public:
    virtual std::shared_ptr<Animal> createDog() = 0;
    virtual std::shared_ptr<Animal> createSheep() = 0;
};

// ==== 水果工厂实现 ====
class ConcreteFruitFactory : public FruitFactory {
public:
    std::shared_ptr<Fruit> createApple() override {
        return std::make_shared<Apple>();
    }
    std::shared_ptr<Fruit> createBanana() override {
        return std::make_shared<Banana>();
    }
};

// ==== 动物工厂实现 ====
class ConcreteAnimalFactory : public AnimalFactory {
public:
    std::shared_ptr<Animal> createDog() override {
        return std::make_shared<Dog>();
    }
    std::shared_ptr<Animal> createSheep() override {
        return std::make_shared<Sheep>();
    }
};

// ==== 工厂选择器 ====
class FactorySelector {
public:
    enum class Type { FRUIT, ANIMAL };

    static std::shared_ptr<AbstractFactory> getFactory(Type type) {
        if (type == Type::FRUIT) {
            return std::make_shared<ConcreteFruitFactory>();
        } else {
            return std::make_shared<ConcreteAnimalFactory>();
        }
    }
};

// ==== 使用示例 ====
int main() {
    // 选择水果工厂
    auto fruitFactory = std::dynamic_pointer_cast<FruitFactory>(
        FactorySelector::getFactory(FactorySelector::Type::FRUIT));
    
    auto apple = fruitFactory->createApple();
    apple->show();

    auto banana = fruitFactory->createBanana();
    banana->show();

    // 选择动物工厂
    auto animalFactory = std::dynamic_pointer_cast<AnimalFactory>(
        FactorySelector::getFactory(FactorySelector::Type::ANIMAL));

    auto dog = animalFactory->createDog();
    dog->voice();

    auto sheep = animalFactory->createSheep();
    sheep->voice();

    return 0;
}

✅ 优点:

  • 遵循开闭原则,支持产品族的统一创建

  • 便于对产品进行分组管理,提高模块间协作性。

❌ 缺点:

  • 系统复杂度提高,类之间的依赖关系增多。

  • 如果要添加新产品(而不是产品族),修改成本大(会破坏工厂接口)。

✅ 适用场景:

  • 一个系统需要成组创建多个互相依赖的对象

  • 比如 GUI 库中,不同操作系统(Windows/Mac/Linux)下的按钮、菜单、文本框需要成套配合。

4.建造者模式

建造者模式是⼀种创建型设计模式, 使⽤多个简单的对象⼀步⼀步构建成⼀个复杂的对象,能够将⼀个复杂的对象的构建与它的表⽰分离,提供⼀种创建对象的最佳⽅式。主要⽤于解决对象的构建过于复杂的问题。
建造者模式主要基于四个核⼼类实现:
• 抽象产品类:定义复杂对象结构、属性和接口
• 具体产品类:⼀个具体的产品对象类
• 抽象Builder类:创建⼀个产品对象所需的各个部件的抽象接⼝
• 具体产品的Builder类:实现抽象接⼝,构建各个部件
• 指挥者Director类:统⼀组建过程,提供给调⽤者使⽤,通过指挥者按顺序来构造产品

抽象产品类:需要设置的属性

具体产品类:不同产品设置的属性不同

抽象Builder类:设置对应的属性的接口

具体产品的Builder类:具体怎么设置产品属性 (实现接口)

指挥者Director类:设置属性的先后顺序

// 1. 抽象产品类
class Computer {
    std::string _board, _display, _os;
    void setBoard(...); void setDisplay(...); virtual void setOs() = 0;
};

// 2. 具体产品类
class MacBook : public Computer {
    void setOs() override { _os = "Mac OS X"; }
};

// 3. 抽象建造者
class Builder {
    virtual void buildBoard(...) = 0;
    virtual void buildDisplay(...) = 0;
    virtual void buildOs() = 0;
    virtual Computer::ptr build() = 0;
};

// 4. 具体建造者
class MacBookBuilder : public Builder {
    Computer::ptr _computer;
    void buildBoard(...) override { _computer->setBoard(...); }
    ...
};

// 5. 指挥者
class Director {
    Builder::ptr _builder;
    void construct(...) {
        _builder->buildBoard(...);
        _builder->buildDisplay(...);
        _builder->buildOs();
    }
};
int main()
{
    Builder* buidler = new MackBookBuilder();
    std::unique_ptr<Director> pd(new Director(buidler));
    pd->construct("英特尔主板", "VOC显⽰器");
    Computer::ptr computer = buidler->build();
    std::cout << computer->toString();
    return 0;
}
角色类名职责描述
抽象产品类Computer定义电脑的组成部分(主板、显示器、系统)以及接口
具体产品类MacBook继承 Computer,实现操作系统的设定
抽象建造者Builder定义构建各部分(主板、显示器、OS)和最终组装的接口
具体建造者MacBookBuilder实现构建过程,封装构建细节,返回构造结果
指挥者(Director)Director控制建造流程,调用建造者接口完成构造

5.代理模式


代理模式指代理控制对其他对象的访问, 也就是代理对象控制对原对象的引⽤。在某些情况下,⼀个对象不适合或者不能直接被引⽤访问,⽽代理对象可以在客⼾端和⽬标对象之间起到中介的作⽤。

代理模式的结构包括⼀个是真正的你要访问的对象(⽬标类)、⼀个是代理对象。⽬标对象与代理对象实现同⼀个接⼝,先访问代理类再通过代理类访问⽬标对象。代理模式分为静态代理、动态代理:
静态代理指的是,在编译时就已经确定好了代理类和被代理类的关系。也就是说,在编译时就已经确定了代理类要代理的是哪个被代理类。
• 动态代理指的是,在运⾏时才动态⽣成代理类,并将其与被代理类绑定。这意味着,在运⾏时才能确定代理类要代理的是哪个被代理类。

实现了一个“租房场景”的静态代理模式

  • 房东(Landlord)被代理对象(目标对象)

  • 中介(Intermediary)代理对象

  • 通过中介代理类来控制、增强对房东租房功能的访问

#include <iostream>
#include <string>

// 抽象租房接口
class RentHouse {
public:
    virtual void rentHouse() = 0;
    virtual ~RentHouse() = default;
};

// 房东类:目标对象(实际提供租房服务)
class Landlord : public RentHouse {
public:
    void rentHouse() override {
        std::cout << "房东:将房子租出去\n";
    }
};

// 中介代理类:代理对象,封装对房东的访问并增强功能
class Intermediary : public RentHouse {
public:
    void rentHouse() override {
        std::cout << "中介:发布招租启示\n";
        std::cout << "中介:带人看房\n";
        _landlord.rentHouse();  // 委托给房东完成真正的租房
        std::cout << "中介:租后负责维修服务\n";
    }

private:
    Landlord _landlord;  // 中介内部持有真实房东对象
};

// 客户端调用
int main() {
    Intermediary intermediary;
    intermediary.rentHouse();  // 客户通过代理租房
    return 0;
}
→ 中介的 rentHouse()
    → 发布招租启事
    → 带人看房
    → 调用房东的 rentHouse()(真正租出)
    → 负责租后维修

房东只做了“租出去”这一件事,其他琐事都由中介代理处理,体现了代理模式“控制访问 + 功能增强”的特性。

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

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

相关文章

JS中实现类似sleep、wait、delay的延时功能

前言 编写代码时很多时候需要进行流程化的操作&#xff0c;各个流程间通常需要等待一定时间&#xff0c;这在很多语言中通常可以使用 sleep 、 wait 、 delay 等函数来实现。JavaScript原生并没有类似的功能&#xff0c;想要延时通常就是使用 setTimeout(functionRef, delay) …

Banana Pi BPI-RV2 RISC-V 路由器开发板发售, 全球首款RISC-V路由器

Banana Pi BPI-RV2 开源路由器是矽昌通信和⾹蕉派开源社区&#xff08;Banana Pi &#xff09;合作设计, 联合打造全球首款RISC-V架构路由器开发板。 这是香蕉派开源社区与矽昌通信继BPI-Wifi5 低成本Wifi5 路由器合作之后的又一力作&#xff0c;为全球开发者与商业客户提供基于…

MAUI项目iOS应用以进 App Store 分发

目录 一.通过Visual Studio分发应用1. 登录Apple 开发者帐户到 Visual Studio2.创建分发证书和配置文件3. 分发应用4. 在App Store Connect 中创建应用程序记录5. 如果你想使用mac发布应用 一.通过Visual Studio分发应用 1. 登录Apple 开发者帐户到 Visual Studio 首先我们要…

CentOS 7系统yum报错解决方案(CentOS 7官方EOL问题修复)

摘要 解决CentOS 7因EOL导致的yum update报错问题&#xff0c;通过替换阿里云镜像源恢复软件安装功能&#xff0c;包含详细操作步骤、操作截图、验证方法与备选镜像源&#xff0c;附有安全风险提示。 一、故障现象与原因分析 1.1 典型报错信息 # 执行yum命令时出现&#xff…

解决Windows update服务启动拒绝访问的问题 | wuauserv 注册表拒绝访问的方法

在某些情况下,为了配置系统更新相关服务(例如禁用 Windows 自动更新),我们需要更改注册表中 wuauserv 项的权限。本教程将带你一步步操作,成功获取并修改权限。 修改注册表路径: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\wuauserv 步骤一:打开注册表编辑…

深入解析 JDK jstack 命令:线程分析的利器

你点赞了吗&#xff1f;你关注了吗&#xff1f;每天分享干货好文。 高并发解决方案与架构设计。 海量数据存储和性能优化。 通用框架/组件设计与封装。 如何设计合适的技术架构&#xff1f; 如何成功转型架构设计与技术管理&#xff1f; 在竞争激烈的大环境下&#xff0c…

【操作系统原理03】处理机调度与死锁

文章目录 大纲一.处理机调度概念与层次0.大纲1.基本概念2.三个层次3.七状态模型4.三层调度都对比与联系 二.进程调度的时机&#xff0c;切换与过程的调度方式0.大纲1.进程调度时机2.调度方式3.进程的切换与过程 三.调度器和闲逛资源1.调度器/调度程序2.闲逛进程 四.调度算法的评…

Quipus,LightRag的Go版本的实现

1 项目简介 奇谱系统当前版本以知识库为核心&#xff0c;基于知识库可以快构建自己的问答系统。知识库的Rag模块的构建算法是参考了LightRag的算法流程的Go版本优化实现&#xff0c;它可以帮助你快速、准确地构建自己的知识库&#xff0c;搭建属于自己的AI智能助手。与当前LLM…

使用 Vite 快速搭建现代化 React 开发环境

1.检查环境 说明&#xff1a;检测环境&#xff0c;node版本为18.20.6。 2.创建命令 说明&#xff1a;创建命令&#xff0c;选择对应的选项。 npm create vitelatest 3.安装依赖 说明&#xff1a;安装相关依赖。 npm i

PG数据库推进医疗AI向量搜索优化路径研究(2025年3月修订版)

PG数据库推进医疗AI向量搜索优化路径研究 一、医疗 AI 向量搜索的发展现状与挑战 1.1 医疗数据特征与检索需求 医疗数据作为推动医疗领域进步与创新的关键要素,具有鲜明且复杂的特征。从多模态角度看,医疗数据涵盖了结构化数据,如患者基本信息、检验检查报告中的数值结果;…

可穿戴经颅多通道直流电刺激产品测试总结

一 概念原理 tDCS 是一种非侵入性的神经调节技术&#xff0c;利用恒定、低强度直流电&#xff08;通常为 0 - 2mA&#xff09;通过电极作用于特定的大脑区域。其工作原理是通过调节神经元的膜电位&#xff0c;来增加或降低神经元兴奋性的特定区域&#xff0c;从而改变大脑运作。…

详解与HTTP服务器相关操作

HTTP 服务器是一种遵循超文本传输协议&#xff08;HTTP&#xff09;的服务器&#xff0c;用于在网络上传输和处理网页及其他相关资源。以下是关于它的详细介绍&#xff1a; 工作原理 HTTP 服务器监听指定端口&#xff08;通常是 80 端口用于 HTTP&#xff0c;443 端口用于 HT…

Moldflow模流分析教程

Moldflow模流分析教程&#xff1a;

计算机网络 3-4 数据链路层(局域网)

4.1 局域网LAN 特点 1.覆盖较小的地理范围 2.较低的时延和误码率 3.局域网内的各节点之间 4.支持单播、广播、多播 分类 关注三要素 &#xff08;出题点&#xff09; ①拓扑结构 ②传输介质 ③介质访问控制方式 硬件架构 4.2 以太网 4.2.1 层次划分 4.2.2 物理层标准…

单片机AIN0、AIN1引脚功能

目录 1. 模拟-数字转换器&#xff08;ADC&#xff09; 2. 交流电源&#xff08;AC&#xff09; 总结 这两部分有什么区别&#xff1f; 在这个电路图中&#xff0c;两个部分分别是模拟-数字转换器&#xff08;ADC&#xff09;和交流电源&#xff08;AC&#xff09;。以下是这…

如何增加 Elasticsearch 中的 primary shard 数量

作者&#xff1a;来自 Elastic Kofi Bartlett 探索增加 Elasticsearch 中 primary shard 数量的方法。 更多阅读&#xff1a; Elasticsearch&#xff1a;Split index API - 把一个大的索引分拆成更多分片 Elasticsearch&#xff1a;通过 shrink API 减少 shard 数量来缩小 El…

Java 并发性能优化:线程池的最佳实践

Java 并发性能优化&#xff1a;线程池的最佳实践 在 Java 并发编程的世界里&#xff0c;线程池堪称提高应用性能与稳定性的神器。恰如其分地运用线程池&#xff0c;能让我们在多线程任务调度时游刃有余&#xff0c;既能避免线程频繁创建销毁带来的开销&#xff0c;又能合理管控…

【综述】一文读懂卷积神经网络(CNN)

卷积神经网络&#xff08;Convolutional Neural Networks, CNN&#xff09;是一类包含卷积计算且具有深度结构的前馈神经网络&#xff08;Feedforward Neural Networks&#xff09;&#xff0c;是深度学习&#xff08;deep learning&#xff09;的代表算法之一。本文旨在介绍CN…

阿里云集群开启debug

1、安装 kubectl Macos brew install kubectl Windows&#xff1a; https://kubernetes.io/zh-cn/docs/tasks/tools/install-kubectl-windows/ 下载后&#xff0c;放到任意目录 2、配置连接信息 mac 将以下内容复制到计算机 $HOME/.kube/config 文件下: windows 不同集…

Unity之如何实现RenderStreaming视频推流

文章目录 前言引入 UnityRenderStreaming 的好处教程步骤 1:设置环境步骤 2: 创建项目步骤 3:安装软件包步骤 5:下载示例步骤 6:检查配置环境步骤 7:打开推流场景步骤 8: 准备用于流式传输的WebServer应用程序步骤 9: 运行 示例场景步骤 10:检查视频是否在浏览器中显示…