《Linux工程实践篇(一):认识设计模式——从日志系统看策略模式》

本文要聊的,是设计模式那点事。先从基本概念入手,把23种常见设计模式挨个过一遍,认认脸。然后以日志系统为例,讲讲日志到底有什么用、该长什么样、又有哪些实现方案。

代码实践这块,重点落在策略模式上,看它怎么在日志组件里落地,又怎么把解耦和扩展这两件事,办得漂漂亮亮。

目录

一、认识设计模式:为什么需要设计模式

[1.1 经典的23种设计模式](#1.1 经典的23种设计模式)

[1.1.1 开发中最常见的设计模式](#1.1.1 开发中最常见的设计模式)

[1.1.2 实际开发中经常遇到的设计模式](#1.1.2 实际开发中经常遇到的设计模式)

[1.1.3 使用场景相对较少的设计模式](#1.1.3 使用场景相对较少的设计模式)

二、从零认识日志系统

[2.1 日志是什么:概念与实际作用](#2.1 日志是什么:概念与实际作用)

[2.2 一条日志应该包含哪些信息](#2.2 一条日志应该包含哪些信息)

[2.3 一个日志系统可以如何实现](#2.3 一个日志系统可以如何实现)

三、日志系统的完整代码与实现分析

[3.1 Log.hpp:日志核心实现](#3.1 Log.hpp:日志核心实现)

[3.2 Mutex.hpp:互斥锁封装](#3.2 Mutex.hpp:互斥锁封装)

[3.3 代码中的问题与优化](#3.3 代码中的问题与优化)

[3.3.1 致命问题:GuardMutex按值传参,实际上没有锁住原对象](#3.3.1 致命问题:GuardMutex按值传参,实际上没有锁住原对象)

[3.3.2 虚析构函数为什么需要virtual](#3.3.2 虚析构函数为什么需要virtual)

[3.3.3 全局Logger对象为什么会违反ODR](#3.3.3 全局Logger对象为什么会违反ODR)

[3.4 策略模式如何落地到Log.hpp](#3.4 策略模式如何落地到Log.hpp)

[3.4.1 抽象策略:LogFlashStrategy](#3.4.1 抽象策略:LogFlashStrategy)

[3.4.2 具体策略一:ConsoleLogStrategy](#3.4.2 具体策略一:ConsoleLogStrategy)

[3.4.3 具体策略二:FileLogStrategy](#3.4.3 具体策略二:FileLogStrategy)

[3.4.4 策略模式为日志系统带来了什么](#3.4.4 策略模式为日志系统带来了什么)


一、认识设计模式:为什么需要设计模式

软件开发这行,规模越做越大,人越来越多。每个人技术底子不一样,踩过的坑也不一样,写出来的代码自然参差不齐。要是每个问题都从零摸索,同一个坑反复踩,那成本就太高了。

于是,前辈们把那些反复出现、又特别典型的软件设计场景拎出来,一个个总结成标准解法。这些被反复验证过的方案,就是设计模式。

说白了,它就是前人留下的经验地图,告诉你在什么路口该往哪拐,别每次都自己硬闯。

1.1 经典的23种设计模式

经典的GoF(Gang of Four)设计模式,一共23种,按套路分成三大类:创建型、结构型、行为型。不过真放到工业界里,出镜率可是天差地别。我们按实际开发中的使用频次,把它们分成三档来看。

1.1.1 开发中最常见的设计模式

这几个,几乎天天打交道,必须烂熟于心。

  • 单例模式(Singleton):一个类,全局只允许有一个实例,还得给外界留一个统一的访问入口。省资源,也省心。

  • 工厂方法模式(Factory Method):把"造对象"这件事交给子类去定,父类只负责定接口。要什么,子类自己看着办。

  • 抽象工厂模式(Abstract Factory):比工厂方法更上一层楼,专门用来造"一族"相关或互相依赖的对象,而且不用指明具体类。

  • 策略模式(Strategy):把一堆算法分别封装起来,让它们能互相替换。本篇的重头戏,后面会细讲。

  • 观察者模式(Observer):对象之间一对多的依赖关系。一个对象状态一变,所有依赖它的对象自动收到通知。

  • 适配器模式(Adapter):把一个类的接口,转换成客户期望的另一个接口。插头不对,加个转换头就好。

  • 装饰器模式(Decorator):动态地给对象叠 buff,加职责。比继承灵活得多,想加就加,想撤就撤。

1.1.2 实际开发中经常遇到的设计模式

这一档,不算天天见,但关键时刻能派上大用场。

  • 建造者模式(Builder):把复杂对象的构建过程和它的表示拆开,同样的构建流程,能造出不同的样子。

  • 代理模式(Proxy):给别的对象找个"替身",由替身来控制对原对象的访问。想加控制、加缓存、加日志,都从这儿下手。

  • 外观模式(Facade):给一整套子系统接口,配一个统一的高层入口。外面看着简单,里面再乱也不关你事。

  • 模板方法模式(Template Method):把算法的骨架定死,某些具体步骤推迟到子类去实现。骨架不动,血肉自填。

  • 组合模式(Composite):把对象拼成树形结构,用来表示"部分-整体"的层次关系。整体和部分,一视同仁。

  • 命令模式(Command):把请求封装成对象,让请求的发起者和执行者彻底解耦。发号施令的,不用管谁去干活。

  • 状态模式(State):对象内部状态一变,行为也跟着变。看起来像是换了个人,其实还是它自己。

1.1.3 使用场景相对较少的设计模式

这些模式,平时不太露面,但面试或特定场景里偶尔会冒出来。

  • 原型模式(Prototype):不从头造,直接拷贝现有的原型对象来生成新对象。

  • 桥接模式(Bridge):把抽象部分和实现部分拆开,让它们各自独立地变化,互不牵制。

  • 享元模式(Flyweight):靠共享技术,撑起大量细粒度对象。省内存的一把好手。

  • 责任链模式(Chain of Responsibility):请求沿着一条处理链往下传,谁接得住谁处理,接不住就往下递。

  • 迭代器模式(Iterator):顺序访问聚合对象里的元素。现代编程语言的标准库大多已经内置了,自己手写的场景反而不多。

  • 中介者模式(Mediator):用一个中介对象,把一堆对象之间的复杂交互全揽过来,降低耦合。

  • 备忘录模式(Memento):在不破坏封装的前提下,把对象的内部状态记下来,将来还能恢复回去。后悔药本药。

  • 访问者模式(Visitor):不改变元素类的前提下,给这些元素定义新的操作。元素不动,操作随便加。

  • 解释器模式(Interpreter):给定一门语言,定义它的文法表示,再配一个解释器。用到的场景比较专,一般业务代码里很少碰。

二、从零认识日志系统

2.1 日志是什么:概念与实际作用

计算机里的日志,说白了就是一本"运行日记"------系统和软件在跑的过程中,发生了什么、出了什么事,一笔一笔都记在上面。它是系统维护、故障排查、安全管理的得力工具,主要干三件事:

  • 监控运行状态:实时盯着程序的执行流程,看看它健不健康、走到哪一步了。

  • 记录异常信息:系统一出错,当场把现场数据抓下来,别让线索溜走。

  • 定位与修复问题:帮程序员快速找到 Bug 的根儿,对症下药,精准修复。

2.2 一条日志应该包含哪些信息

设计一套标准的日志系统,先得把输出格式定清楚。通常来说,分核心必选和扩展可选两档。

核心必选指标:

  • 时间戳:日志是什么时候产生的,精确记录。

  • 日志等级:这条日志有多严重?DEBUG、INFO、WARNING、ERROR、FATAL,标清楚。

  • 日志内容:具体描述了什么,文本也好,数据细节也罢,得说明白。

扩展可选指标:

  • 文件名与行号:精准定位到产生日志的那一行源码,省得满项目翻。

  • 进程与线程 ID:多线程或并发环境下,一眼区分是哪个执行流在说话。

2.3 一个日志系统可以如何实现

实际开发里,现成的优秀日志库一抓一大把,spdlog、glog、Boost.Log、Log4cxx,随便挑一个都能用。但为了把日志系统的设计思想吃透,我们这个项目不打算直接拿现成的,而是自己动手写一个。写的时候,会引入设计模式里的策略模式,让日志的输出行为灵活可换,想怎么打就怎么打。

三、日志系统的完整代码与实现分析

整套日志系统由两个头文件撑起来:Log.hpp负责日志的核心逻辑与策略模式,Mutex.hpp负责互斥锁的封装。下面逐块拆开看。

3.1 Log.hpp:日志核心实现

这个文件是整个日志系统的心脏。它把日志的格式、等级、输出策略、刷盘方式,全都包了进去。

cpp 复制代码
#ifndef __LOG_HPP__
#define __LOG_HPP__
#include "Mutex.hpp"
#include <filesystem>
#include <iostream>
#include <ctime>
#include <string>
#include <sys/types.h>
#include <unistd.h>
#include <fstream>
#include <sstream>
#include <memory>
#include <cstdio>

namespace LogModule
{
    const std::string CAGE = "\n";

    // 日志刷盘策略的抽象基类
    class LogFlashStrategy
    {
    public:
        ~LogFlashStrategy() = default;
        virtual void SyncLog(const std::string& message) = 0;
    };

    // 控制台输出策略
    class ConsoleLogStrategy : public LogFlashStrategy
    {
    public:
        void SyncLog(const std::string& logmessage) override
        {
            MyMutex::GuardMutex _guardmutex(_mutex);
            std::cout << logmessage << CAGE;
        }
    private:
        MyMutex::Mutex _mutex;
    };

    const std::string default_filename = "log.txt";
    const std::string default_filepath = "./log";

    // 文件输出策略
    class FileLogStrategy : public LogFlashStrategy
    {
    public:
        FileLogStrategy(const std::string file = default_filename,
                        const std::string path = default_filepath)
            : _filename(file), _filepath(path)
        {
            MyMutex::GuardMutex _guardmutex(_mutex);
            if (std::filesystem::exists(_filepath))
                return;
            try
            {
                std::filesystem::create_directories(_filepath);
            }
            catch (const std::filesystem::filesystem_error& _exception)
            {
                std::cout << _exception.what() << CAGE;
            }
        }

        void SyncLog(const std::string& logmessage)
        {
            MyMutex::GuardMutex _guardmutex(_mutex);
            std::string pathfile = _filepath +
                (_filepath.back() == '/' ? "" : "/") + _filename;
            std::ofstream out(pathfile, std::ios::app);
            if (!out.is_open())
                return;
            out << logmessage << CAGE;
            out.close();
        }
    private:
        MyMutex::Mutex _mutex;
        std::string _filename;
        std::string _filepath;
    };

    // 日志等级
    enum class LEVEL
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    // 获取当前时间字符串
    std::string GetCurrentTime()
    {
        time_t cur_time = time(nullptr);
        struct tm result;
        localtime_r(&cur_time, &result);
        char timebuffer[128];
        snprintf(timebuffer, sizeof(timebuffer), "%4d-%02d-%02d %02d:%02d:%02d",
                 result.tm_year + 1900,
                 result.tm_mon + 1,
                 result.tm_mday,
                 result.tm_hour,
                 result.tm_min,
                 result.tm_sec);
        return timebuffer;
    }

    // 等级转字符串
    std::string LevelToString(LEVEL level)
    {
        switch (level)
        {
        case LEVEL::DEBUG:   return "DEBUG";
        case LEVEL::INFO:    return "INFO";
        case LEVEL::WARNING: return "WARNING";
        case LEVEL::ERROR:   return "ERROR";
        case LEVEL::FATAL:   return "FATAL";
        default:             return "UNKNOWN";
        }
    }

    class Logger
    {
    public:
        Logger()
        {
            EnableConsolLogStrategy();
        }

        void EnableConsolLogStrategy()
        {
            _current_flash_tsrategy = std::make_unique<ConsoleLogStrategy>();
        }

        void EnableFileLogStrategy()
        {
            _current_flash_tsrategy = std::make_unique<FileLogStrategy>();
        }

        // 内部类:一条日志消息
        class LogMessage
        {
        public:
            LogMessage(LEVEL& level, std::string& file, size_t line, LogModule::Logger& logger)
                : _current_time(GetCurrentTime()),
                  _level(level),
                  _pid(getpid()),
                  _src_file(file),
                  _line(line),
                  _logger(logger)
            {
                std::stringstream ret;
                ret << "[" << _current_time << "]"
                    << "[" << LevelToString(_level) << "]"
                    << "[" << _pid << "]"
                    << "[" << _src_file << "]"
                    << "[" << _line << "] -";
                _hole_message = ret.str();
            }

            template<class T>
            LogMessage& operator<<(const T& info)
            {
                std::stringstream ss;
                ss << info;
                _hole_message += ss.str();
                return *this;
            }

            ~LogMessage()
            {
                if (_logger._current_flash_tsrategy)
                    _logger._current_flash_tsrategy->SyncLog(_hole_message);
            }
        private:
            std::string _current_time;
            LEVEL _level;
            pid_t _pid;
            std::string _src_file;
            size_t _line;
            std::string _hole_message;
            LogModule::Logger& _logger;
        };

        LogMessage operator()(LEVEL level, std::string filename, size_t line)
        {
            return LogMessage(level, filename, line, *this);
        }
    private:
        std::unique_ptr<LogFlashStrategy> _current_flash_tsrategy;
    };

    Logger logger;

#define LOG(level) logger(level, __FILE__, __LINE__)
#define ENABLE_CONSOLE_LOG_STRATEGY() logger.EnableConsolLogStrategy()
#define ENABLE_FILE_LOG_STRATEGY() logger.EnableFileLogStrategy()
}
#endif

核心设计拆解:

策略模式是本文件的灵魂。 LogFlashStrategy定义了刷盘策略的抽象接口,ConsoleLogStrategy和FileLogStrategy各自实现。Logger里揣着一个unique_ptr<LogFlashStrategy>,运行时想切哪个策略,调一下EnableXXX就行。日志输出到哪儿,跟日志怎么组织、怎么调用,彻底解耦。

**LogMessage是个RAII风格的临时对象。**LOG(LEVEL::INFO) << "hello" << 42这行代码,会先构造一个LogMessage,把时间戳、等级、PID、文件名、行号拼进_hole_message。接着 << 运算符把内容往上追加。等这条语句结束,LogMessage析构,析构函数里调SyncLog,一条日志就刷出去了。整条日志的拼装和输出,靠对象的生命周期自动完成,用起来像流水一样自然。

宏LOG负责偷懒。 把__FILE__和__LINE__自动塞进去,调用方只需要关心等级和内容,文件名行号这些"体力活",宏替你干了。

3.2 Mutex.hpp:互斥锁封装
cpp 复制代码
#ifndef __MUTEX_HPP__
#define __MUTEX_HPP__
#include <pthread.h>

namespace MyMutex
{
    class Mutex
    {
    public:
        Mutex() { pthread_mutex_init(&_mutex, nullptr); }
        void Lock() { pthread_mutex_lock(&_mutex); }
        void UnLock() { pthread_mutex_unlock(&_mutex); }
        ~Mutex() { pthread_mutex_destroy(&_mutex); }
    private:
        pthread_mutex_t _mutex;
    };

    class GuardMutex
    {
    public:
        GuardMutex(Mutex mutex)
            : _mutex(mutex)
        {
            _mutex.Lock();
        }
        ~GuardMutex()
        {
            _mutex.UnLock();
        }
    private:
        Mutex _mutex;
    };
}
#endif

Mutex是基础互斥量的薄封装。 构造初始化、析构销毁,Lock/UnLock包住pthread_mutex_lock/unlock,用起来省心。

GuardMutex是RAII守卫。 构造即加锁,析构即解锁,离开作用域自动释放。思路是对的,但这份实现里藏着几个严重问题,我们放到3.3里专门说。

3.3 代码中的问题与优化

框架和设计思路都没问题,策略模式用得很地道,RAII日志消息也漂亮。但代码里有几处硬伤,有的会直接导致功能失效,有的是隐藏的性能坑。

3.3.1 致命问题:GuardMutex按值传参,实际上没有锁住原对象
cpp 复制代码
class GuardMutex
{
public:
    GuardMutex(Mutex mutex)      // 按值传递!拷贝了一份互斥量
        : _mutex(mutex)           // 又拷贝了一份
    {
        _mutex.Lock();
    }
    ~GuardMutex()
    {
        _mutex.UnLock();
    }
private:
    Mutex _mutex;                 // 按值成员,再拷贝一份
};

pthread_mutex_t是系统资源,不是普通对象。按值拷贝一个Mutex,等于在内存里克隆了一个毫无关联的锁。原锁和副本锁,彼此不认。你锁住的是副本,别的线程锁的是原锁,两边各锁各的,互斥保护完全失效。

更糟的是,pthread_mutex_t的拷贝行为本身就是未定义的。拷贝一份互斥量,在 POSIX 标准里根本不允许。

正确写法,必须传引用、存引用:

cpp 复制代码
class GuardMutex
{
public:
    GuardMutex(Mutex& mutex)     // 传引用
        : _mutex(mutex)           // 存引用
    {
        _mutex.Lock();
    }
    ~GuardMutex()
    {
        _mutex.UnLock();
    }
private:
    Mutex& _mutex;                // 引用成员
};
3.3.2 虚析构函数为什么需要virtual
cpp 复制代码
class LogFlashStrategy
{
public:
    ~LogFlashStrategy() = default;   // 没有 virtual!
    virtual void SyncLog(const std::string& message) = 0;
};

Logger里用的是std::unique_ptr<LogFlashStrategy>,指向派生类对象。如果基类析构函数不是虚函数,delete一个基类指针时,派生类的析构函数不会被调用。派生类里那些成员,比如FileLogStrategy里的Mutex,就没人清理,资源泄漏。

改成:

cpp 复制代码
virtual ~LogFlashStrategy() = default;
3.3.3 全局Logger对象为什么会违反ODR
cpp 复制代码
Logger logger;   // 定义在头文件里

头文件被多个.cpp包含时,每个翻译单元都会生成一份logger的定义,链接时直接冲突,重复定义。要么改成inline,要么放进.cpp里,要么加static(但那样每个编译单元一份,不共享)。

3.4 策略模式如何落地到Log.hpp

在日志模块的实际落地里,有两件事必须分开看:一件是日志消息怎么生成、怎么格式化;另一件是日志刷完之后往哪儿送,终端、磁盘,还是网络。这是两个独立的关注点,硬捆在一起,代码迟早变成一团乱麻。

策略模式出场,就是来拆这个结的。它把"日志刷新"抽象成一个统一的策略接口,让日志系统做到对扩展开放、对修改封闭。想加一种新的刷盘方式?加个新策略就行,原有代码一行不动。

3.4.1 抽象策略:LogFlashStrategy

LogFlashStrategy是所有刷盘策略的基类,也就是那个"抽象接口"。它定下了一条铁律,所有具体策略,都必须实现统一的刷新行为。

cpp 复制代码
class LogFlashStrategy
{
public:
    virtual ~LogFlashStrategy() = default;
    virtual void SyncLog(const std::string& message) = 0;
};

两个细节,各有讲究。

**虚析构函数:**确保通过基类指针销毁派生类策略对象时,能正确调用子类的析构函数,防止内存泄漏。Logger里用的是unique_ptr<LogFlashStrategy>,少了这个virtual,派生类的资源就没人收拾。

**纯虚函数SyncLog:**定义日志刷新的标准接口。它接收已经格式化好的日志字符串,至于具体怎么刷、刷到哪儿,全部延迟到子类里实现。接口定死,实现自由。

3.4.2 具体策略一:ConsoleLogStrategy

ConsoleLogStrategy继承自LogFlashStrategy,负责把日志同步刷新到终端控制台。

cpp 复制代码
class ConsoleLogStrategy : public LogFlashStrategy
{
public:
    void SyncLog(const std::string& logmessage) override
    {
        MyMutex::GuardMutex _guardmutex(_mutex);
        std::cout << logmessage << CAGE;
    }
private:
    MyMutex::Mutex _mutex;
};

**线程安全保证:**多线程并发打印日志时,控制台输出极容易字符穿插、乱成一团。这里用RAII机制的锁GuardMutex把输出过程整个罩住,保证每一条日志都能完整、连贯地落到屏幕上,不会你半句我半句。

3.4.3 具体策略二:FileLogStrategy

FileLogStrategy同样继承自LogFlashStrategy,负责把日志持久化写入本地磁盘。

cpp 复制代码
const std::string default_filename = "log.txt";
const std::string default_filepath = "./log"; // ./log/log.txt

class FileLogStrategy : public LogFlashStrategy
{
public:
    FileLogStrategy(const std::string file = default_filename,
                    const std::string path = default_filepath)
        : _filename(file), _filepath(path)
    {
        MyMutex::GuardMutex _guardmutex(_mutex);
        if (std::filesystem::exists(_filepath))
            return;
        try
        {
            std::filesystem::create_directories(_filepath);
        }
        catch (const std::filesystem::filesystem_error& _exception)
        {
            std::cout << _exception.what() << CAGE;
        }
    }

    void SyncLog(const std::string& logmessage) override
    {
        MyMutex::GuardMutex _guardmutex(_mutex);
        std::string pathfile = _filepath +
            (_filepath.back() == '/' ? "" : "/") + _filename;
        std::ofstream out(pathfile, std::ios::app);
        if (!out.is_open())
            return;
        out << logmessage << CAGE;
        out.close();
    }
private:
    MyMutex::Mutex _mutex;
    std::string _filename;
    std::string _filepath;
};

三个设计点,各有各的用处。

**目录自动创建:**构造函数里用C++17的std::filesystem::create_directories,先检测、后创建,路径不存在就给你搭好。日志目录这种事,不该让用户手动去建,代码自己搞定,健壮性直接上来了。

**追加写入模式:**SyncLog里用std::ios::app打开文件,新日志老老实实追加到文件末尾,绝不覆盖已有记录。日志这东西,最忌讳的就是写着写着把前面的给冲了。

**并发文件保护:**写入前后同样加锁。多个线程同时往同一个文件里写,不加锁,内容穿插、数据损坏,谁也跑不掉。一把锁下去,写文件的动作变成串行,内容完整,数据安全。

这就是策略模式在日志系统里的真实价值:把"怎么刷"和"刷到哪"彻底拆开,让变化的部分独立、稳定,让不变的部分复用、沉淀。

3.4.4 策略模式为日志系统带来了什么

核心逻辑与刷盘细节彻底解耦。 Logger手里只攥着一个LogFlashStrategy的基类指针,要刷盘的时候,直接一句SyncLog甩过去。至于底层到底是打印到控制台,还是写入磁盘,它一概不关心,也压根不需要知道。各管各的,谁也不越界。

扩展性极佳,完美贴合开闭原则(OCP)。 后面要是想加新的刷盘方式------比如按日志等级拆文件存储的GroupLogStrategy,或者把日志通过网络发出去的NetLogStrategy------只需要继承LogFlashStrategy,把新的策略类实现出来就行。现有的控制台策略、文件策略,一行代码都不用动。对扩展开放,对修改封闭,这句话在日志系统里落得实实在在。

业界最通用的五种核心日志等级

日志等级这东西,得有个统一的标尺,不然一条日志出来,谁也不知道它到底有多严重。下面这五档,是业界最通用的划分:

| 日志等级 | 代表含义 | 典型应用场景 | 影响范围与处理建议 |
| DEBUG | 调试信息 | 详细的函数入参、中间变量值、执行路径等细节。 | 仅用于开发调试,生产环境默认关闭。 |
| INFO | 正常信息 | 系统启动/关闭、关键业务流程节点、成功交易通知。 | 记录系统正常运行轨迹,供日常运维与留痕查验。 |
| WARN/WARNING | 潜在警告 | 接口重试成功、配置参数缺省、资源利用率过高。 | 系统尚能继续运行,但存在安全隐患,需定期排查。 |
| ERROR | 局部错误 | 捕获到非预期异常、单次请求失败、数据库超时。 | 局部功能受损但未导致整体宕机,需尽快定位修复。 |

FATAL/CRITICAL 致命崩溃 内存溢出(OOM)、核心服务宕机、主数据库断开。 服务彻底不可用或系统崩溃,必须触发紧急告警并人工抢修。

从DEBUG到FATAL,严重程度一路往上走。DEBUG 是给开发者自己看的,生产环境里基本关着;INFO记录系统正常走路的轨迹;WARN是系统还能跑,但得留个心眼;ERROR是局部出了问题,得赶紧查;FATAL就是天塌了,必须立刻拉响警报,人工上场抢修。

五档分级,各司其职。一条日志出来,扫一眼等级,就知道该用什么姿态应对,这就是分级的意义。


如果这篇文章对你有帮助,别忘了点个赞、点个收藏、点个关注。你的每一次反馈,都是我继续硬核输出的最大动力。我们下篇见。

相关推荐
工作10年+,存储芯片行业1 小时前
Linux NVMe 中断排查与性能优化:CPU 亲和性
linux·运维·服务器·windows·性能优化·ssd·pcie
小蒋观天下1 小时前
端侧大模型在安防摄像头部署实操(上)|行业痛点、架构选型、落地思路解析
大数据·人工智能·安全·计算机视觉·ai大模型
wuminyu1 小时前
ForkJoinPool内部WorkQueue的Lock-Free数组操作以及并发任务窃取原理剖析
java·linux·c语言·jvm·c++
林伽一1 小时前
能力趋同、账单分化,技术选型正在从榜单转向负载|2026年10月06日
人工智能·科技·安全·ai
她说彩礼65万2 小时前
C语言 堆区和栈区
java·linux·c语言
Zguigo2 小时前
【CUDA8】第一个CUDA C++ Kernel --vector add
java·开发语言·c++
一号弯2 小时前
装完LINUX,请先新建日常用户
linux·运维·服务器
-Marks-2 小时前
【C++编程】STL容器(二)--- vector底层模拟实现(常用接口实现 | 扩容机制 | 深浅拷贝 | 迭代器失效)
开发语言·c++·vector·内存管理·迭代器失效·vector底层模拟实现
驭渊的小故事2 小时前
linux 基础命令 + git 仓库创建和配置命令
linux·git