C++11魔法静态变量:手把手实现线程安全单例模板
单例模式大概是设计模式里写法最多、争议也最多的一个尤其在 C 里没有统一的内存模型保障时线程安全全靠手写。前些年我接一个遗留模块线上日志系统里出现过诡异现象两个线程各自调用同一个单例方法拿到的却是两个地址不同的“唯一”对象相互覆盖配置查了半天才发现是懒汉式单例在多线程下踩了雷。C11 标准发布之后情况才真正变了——静态局部变量的初始化被标准明确保证线程安全连带着把单例模式里最难的“惰性初始化 多线程”给彻底解决了。这篇文章就来聊聊我目前最常用的一套方案基于 C11 magic static 的线程安全单例以及怎么用一个泛型单例模板让项目里每个全局唯一对象不用再复制粘贴那套容易写错的代码。1. 饿汉、懒汉与双重检查锁经典单例各自的软肋要理解 C11 带来的变化得先回头看看传统写法到底难在哪。先看最经典的饿汉式class Config { public: static Config get() { return instance_; } Config(const Config) delete; Config operator(const Config) delete; private: Config() default; static Config instance_; }; Config Config::instance_;饿汉式的思路是在 main 执行之前就把对象构造好静态成员变量instance_的初始化发生在程序启动阶段这时候还处于单线程环境所以不存在竞争。这确实是最简单的线程安全方案。但它有两个明显代价。第一不支持延迟加载。哪怕程序从头到尾根本没用过Config启动时也得白白构造一次如果构造函数里有文件读取、网络连接、数据库初始化这类重操作那无辜的启动时间就上去了。第二跨编译单元的静态初始化顺序不可控。如果Config::instance_的构造函数里依赖了另一个编译单元的静态对象而那个对象还没初始化就会踩到经典的“静态初始化顺序惨案”。虽然可以用 “construct on first use” 的 reinterpret_cast 技巧绕但代码已经不好看了。于是很多人转向懒汉式也就是第一次调用时才创建class Config { public: static Config get() { static Config* inst nullptr; // C98 时代这行和下面的判断组合在一起并不安全 if (inst nullptr) { inst new Config(); } return *inst; } private: Config() default; };单线程环境下这代码完全没问题但多线程第一次同时进入时多个线程可能都看到inst nullptr都执行new Config()最后指针被覆盖既有内存泄漏又可能产生多个实例。加一坨全局锁当然能解决但每次读配置都要走一次互斥量访问频率高时明显不划算。当年最著名的优化就是双重检查锁Double-Checked Locking Patternclass Config { public: static Config* get() { if (inst_ nullptr) { // 第一次检查避免竞争时频繁加锁 std::lock_guardstd::mutex lock(mutex_); if (inst_ nullptr) { // 第二次检查真正创建 inst_ new Config(); } } return inst_; } private: static std::mutex mutex_; static Config* inst_; };这个模式在 Java、C# 里被广泛使用但在 C98/03 时代却是标准意义上的未定义行为new Config()可以分为分配内存、调用构造函数、把地址写入inst_三个步骤编译器和 CPU 都可能重新排序。假设线程 A 先写入了inst_指针但构造函数还没跑完线程 B 在外层检查时发现指针非空直接拿去用拿到的就是一个半构造对象。当年很多跨平台项目都在这里翻车不是因为程序员的逻辑不对而是底层没有提供内存屏障保证。C11 之后用std::atomicConfig*配合 acquire/release 内存序确实能修好双重检查锁但代码明显更啰嗦而且要写出完全正确的高性能版本需要理解memory_order_acquire、memory_order_release的配套关系稍微一疏忽又回到 UB。所以我的结论很直接既然有更好的工具就别再手搓双重检查锁了。方案延迟加载线程安全额外成本适用性饿汉式不支持是启动即构造可能拖慢启动构造很轻、对启动时延不敏感懒汉式 每次加锁支持是每次访问都加锁访问频率低双重检查锁支持C11 前不安全代码复杂容易写错不推荐Meyers Singleton支持C11 后由标准保证几乎为零推荐2. 静态局部变量C11 对线程安全的正面回应Meyers Singleton 的写法其实早在 C98 就出现了但那时候没人敢保证它线程安全。我们来重看核心代码class Config { public: static Config get() { static Config inst; // 关键就这一行 return inst; } private: Config() default; };这段代码的安全依据来自 C11 标准 [stmt.dcl] 第 4 条如果变量在首次经过声明时正在进行初始化而此时另一个控制流也进入该声明并发执行必须等待初始化完成。也就是说编译器和运行时系统有义务保证同一个局部静态变量的初始化操作在多个线程同时触发时只会有一个线程真正执行初始化其他线程都会阻塞到它完成。为了做到这一点编译器会为每个需要动态初始化的局部静态变量生成一个隐藏的 guard 变量并插入类似下面的运行时调用movzbl guard, %eax testb %al, %al jne .Lalready_init ; 已初始化直接跳到完整对象获取 call __cxa_guard_acquire ; 未初始化尝试获取初始化权 testl %eax, %eax je .Lalready_init movl $0, %esi call __cxa_guard_release ; 初始化完成释放并标记完成在已初始化的快速路径上每次调用通常只是一次字节读取加一次条件跳转不需要进入系统调用、不需要加 pthread 锁这也是为什么它比每次访问都用互斥量的方案快得多。初始化阶段__cxa_guard_acquire内部会做原子判断和阻塞保证只有一个线程进入构造函数如果构造函数抛出了异常运行时还会调用__cxa_guard_abort把 guard 复位下次调用重新尝试初始化。这里有个特别容易被误解的点标准只保证静态局部变量“初始化”本身是线程安全的完全不保证这个对象后续所有成员函数、成员变量都是线程安全的。比如class Counter { public: static Counter get() { static Counter inst; return inst; } void increment() { count_; } int value() const { return count_; } private: Counter() default; int count_ 0; };两个线程同时调increment()count_照样会丢更新因为编译器没有义务为普通成员操作插入原子指令。magic static 解决的是“对象到底创建几次”的问题对象被多线程共享之后内部状态保护仍然是开发者自己的责任。如果需要一个线程安全的计数器请在Counter内部用std::atomicint或者加锁不要让单例模式背这个锅。3. 泛型单例模板把单例能力沉淀成公共底座解决了线程安全问题下一个烦恼很快就来了项目里往往不止一个单例。配置文件是一个、日志系统是一个、线程池管理可能又是一个。每个类都写一遍static XXX get() { static XXX inst; return inst; }代码倒是不长但反复出现以后有人会在复制时改错类名有人会忘记删拷贝构造还有人会自作主张把构造函数改成 public导致外部能直接new出第二个实例。这时候很自然会想到为什么不把“单例”这个能力抽成一个模板我用的是 CRTP奇异递归模板方案模板参数就是最终那个类本身#include utility template typename T class Singleton { public: Singleton(const Singleton) delete; Singleton operator(const Singleton) delete; Singleton(Singleton) delete; Singleton operator(Singleton) delete; template typename... Args static T GetInstance(Args... args) { static T instance{std::forwardArgs(args)...}; return instance; } protected: Singleton() default; ~Singleton() default; };使用它的类长这样#include iostream #include string class Config final : public SingletonConfig { friend class SingletonConfig; public: std::string GetConfigPath() const { return path_; } private: explicit Config(std::string path config.ini) : path_(std::move(path)) {} ~Config() default; std::string path_; };调用方式int main() { auto cfg Config::GetInstance(app.ini); auto cfg_again Config::GetInstance(); std::cout cfg.GetConfigPath() std::endl; // app.ini std::cout (cfg cfg_again) std::endl; // 1同一个对象 }这里有几个设计点需要好好解释。第一为什么是friend class SingletonConfig。GetInstance在SingletonConfig作用域里写了static Config instance编译器要在这里调用Config的构造函数。如果我坚持把Config构造函数设为私有就必须让SingletonConfig成为Config的友元否则编译直接报“无法访问私有成员”。这个 friend 声明相当于显式授权我愿意让 Singleton 模板帮我创建这个唯一对象但绝不允许外部随便new Config()。外部没有friend关系连delete Config::GetInstance()都做不到因为析构函数同样是私有的。第二static T instance的作用域。它写在GetInstance函数内部是一个函数级静态变量。每实例化一份SingletonConfig的GetInstance就会有自己独立的static Config instance如果项目里还有class Logger final : public SingletonLogger又会有独立的static Logger instance。每个类的单例对象互不干扰这正是泛型复用想达到的效果。第三带参数的GetInstance怎么处理第二次调用。static T instance{std::forwardArgs(args)...}只在第一次调用时生效之后无论再传什么参数都会被忽略。所以第一次传入的app.ini生效后面再写Config::GetInstance(another.ini)也改不了它。这个语义符合单例直觉但要记得在注释里写清楚不然团队里很容易有人以为后续传参能更新配置。如果嫌 CRTP 的 friend 声明麻烦还有个更轻的模板函数式写法template typename T, typename... Args T GetSingleton(Args... args) { static T instance{std::forwardArgs(args)...}; return instance; }用起来就是auto cfg GetSingletonConfig(app.ini);完全不要求Config继承任何基类。代价是你没法强制Config的构造函数私有外部仍然可以绕过GetSingleton自己创建对象单例约束全靠约定。我的经验是小项目、内部工具类可以接受模板函数式稍微正式一点、希望“这个类真的不能被外部 new”的场景直接用 CRTP 版本约束固化在类型系统里。这里再补一个完整可编译的小例子包括两个单例类的使用方式#include iostream #include string #include utility template typename T class Singleton { public: Singleton(const Singleton) delete; Singleton operator(const Singleton) delete; template typename... Args static T GetInstance(Args... args) { static T instance{std::forwardArgs(args)...}; return instance; } protected: Singleton() default; ~Singleton() default; }; class Logger final : public SingletonLogger { friend class SingletonLogger; public: void log(const std::string msg) { std::cout [log] msg std::endl; } private: Logger() default; ~Logger() default; }; class Config final : public SingletonConfig { friend class SingletonConfig; public: const std::string path() const { return path_; } private: explicit Config(std::string p config.ini) : path_(std::move(p)) {} ~Config() default; std::string path_; }; int main() { auto cfg Config::GetInstance(app.ini); Logger::GetInstance().log(config path is cfg.path()); return 0; }保存成singleton_demo.cpp用g -stdc11 -O2 singleton_demo.cpp -o singleton_demo编译运行后能看到日志输出没有告警也没有崩溃。唯一要留意的是Logger::log里访问了cfg.path()这在main中是很自然的调用顺序如果反过来在Config构造里调用Logger::GetInstance()就要考虑两个单例的依赖方向这一点下面专门讲。4. 并发实测与开销拆解GetInstance 在多次调用后的表现光说 magic static 线程安全不够我做了一次简单的并发验证8 个线程同时高频调用Config::GetInstance()每个线程循环一百万次最后确认所有线程拿到的对象地址一致。验证代码大致如下#include atomic #include cassert #include thread #include vector int main() { auto first Config::GetInstance(); std::atomicConfig* last{nullptr}; std::vectorstd::thread workers; for (int i 0; i 8; i) { workers.emplace_back([] { for (int j 0; j 1000000; j) { auto c Config::GetInstance(); last.store(c, std::memory_order_relaxed); } }); } for (auto t : workers) { t.join(); } assert(last.load(std::memory_order_relaxed) first); return 0; }这个测试跑下来不会触发断言失败。它验证的关键点只有一个并发首次初始化时9 个执行流main 先调过一次算一个最终都指向同一个对象。顺手也做了个性能趋势对比。测试环境大致是 x86_64 Linux、gcc 9.3、-O2、8 线程高频调用结果只代表相对趋势换机器和编译器会有出入方案8 线程各 200 万次调用耗时约说明Meyers Singletonmagic static0.23s快速路径通常是一次字节读加分支std::call_once版本0.51s每次调用都要检查 once_flag 状态每次加互斥量3.6s8 线程争用同一把锁成本最高std::call_once的版本大概长这样它也是线程安全的class Config { public: static Config get() { static Config* inst nullptr; std::call_once(flag_, [] { inst new Config(); }); return *inst; } private: static std::once_flag flag_; };std::call_once是标准备选方案核心优势在于可以一次初始化多个东西灵活性更高。但对单例这种“初始化完就再没其他事”的场景magic static 更简洁访问开销也略低。另一个细节是std::call_once如果回调抛异常flag_会被复位下次调用会重试magic static 在构造函数抛异常时同样会重试两者的异常安全策略是一致的。看反汇编还能发现另一个有意思的点。高频率调用 magic static 时函数里通常会先做一个快速的 guard 检查如果对象已经初始化根本不会进入__cxa_guard_acquire的慢路径。这跟一个普通的原子读分支差不多几乎不增加额外负担。很多人担心“编译器魔法”会在临界区加锁拖慢性能实际在绝大多数情况下这个开销比手动加锁要小得多。我个人的建议是理解 magic static 的原理是值得的但日常写代码时不需要去纠结它是不是“最优”。多线程单例的访问频率如果真的高到需要计较那几十纳秒更该做的是重新审视架构——比如把高频读取的数据做成不可变对象或者批量拉取到线程本地而不是把宝押在一次 GetInstance 的指令数上。5. 单例的隐形地雷销毁顺序、析构依赖与资源清理线程安全只是单例的第一关C 里还有一道更隐蔽的关卡生命周期与销毁顺序。先看一个典型的崩溃现场。假设项目里有两个单例Config保存配置Router注册路由信息Router析构时想把当前路由表写回配置class Router final : public SingletonRouter { friend class SingletonRouter; public: ~Router() { Config::GetInstance().WriteRoutes(...); // 危险 } private: Router() default; };假如Config在Router之前构造程序结束时销毁顺序是构造的逆序——Router先销毁此时Config还活着看起来没问题。但单例的构造顺序取决于第一次被访问的先后如果某个模块先调用了Router::GetInstance()再调用Config::GetInstance()那销毁顺序就反过来了Config先销毁Router析构时再去调Config::GetInstance()就可能访问一个已经销毁的静态对象轻则读到脏数据重则直接崩溃。更麻烦的是这种问题往往只在特定启动路径、特定退出流程下偶现排查成本极高。所以我在团队里立了一条硬性规则单例的构造函数和析构函数里绝对不允许调用其他单例。所有跨单例依赖都推迟到对象构造完成之后的普通成员函数里做至少在常规调用时依赖对象的生命周期还是稳定的。如果现有代码已经形成了这种析构期依赖短期内又没法重构有一个务实办法让单例“永不销毁”。template typename T class Singleton { public: template typename... Args static T GetInstance(Args... args) { static T* instance new T{std::forwardArgs(args)...}; return *instance; } protected: Singleton() default; ~Singleton() default; };这里静态变量从T对象本身换成了T*指针。指针是平凡可销毁的程序退出时不会触发T的析构函数于是所有单例对象都留在堆上直到操作系统回收内存。好处是彻底回避了析构顺序问题代价是内存检测工具会报告“泄漏”而且如果T持有文件句柄、数据库连接、共享锁等需要主动释放的资源这种写法就无法正常清理。要不要用“永不销毁”本质是工程取舍。对我来说长驻服务进程里如果单例之间依赖关系已经复杂到理不清用永不销毁换一个稳定的进程退出比在析构函数里战战兢兢地猜顺序更实际。但对于客户端程序进程结束后的文件缓冲、日志落盘往往必须写干净“永不销毁”就不合适了。这时候应该从设计上避免单例互相依赖而不是用生命周期方案掩盖。另外构造函数抛异常后重试这个细节平时不容易被注意到但它很重要。如果Config的构造函数里加载了一个不存在的配置文件并抛异常下次有人调用Config::GetInstance()时还会再尝试构造。这既是好消息也是坏消息——好处是故障具有自愈能力坏处是反复失败会反复抛异常。一个成熟的单例构造函数应该具备失败语义比如把“加载失败”记录为故障状态而不是裸抛异常让调用方知道发生了异常避免调用栈堆叠。6. 我现在的取舍标准与几条工程铁律用了这么多年单例我现在的取舍标准已经不像以前那么机械。单例该不该用、怎么写完全取决于对象属性的生命周期特征如果一个对象在进程生命周期内确实只有一个而且全局共享访问是合理的那它天然适配单例如果对象只是在某个模块内共享那更应该用的是依赖注入——把对象作为参数传给需要它的函数而不是到处调GetInstance()。无状态工具类是最典型的不该用单例的反例。比如字符串转换、数值格式化这类函数做成命名空间下的普通函数或者 static 成员函数就够了完全不需要单例包装namespace StringUtils { inline std::string Trim(const std::string s) { /* ... */ } }这种形式不需要实例化没有全局状态也不存在多线程竞争比任何单例都清爽。只有当你确实需要维护一份共享的可变状态、且这份状态生命周期与进程一致时单例才是正解。如果决定用单例我一般会按以下铁律来约束代码统一走泛型单例模板不手写各自版本 项目里所有单例类继承同一个SingletonT团队成员只需要理解一套机制。不然一个项目里三种单例写法排查问题时要切换三种心智模型。构造函数和析构函数能私有就私有 私有构造 friend class SingletonT是防止外部 new 出第二个实例的最强约束。只靠注释让调用方“自觉”迟早有人不自觉。构造和析构里不跨单例调用 跨单例依赖统一放到普通成员函数中把生命周期问题暴露在可排查的调用路径上而不是被压到静态析构的 hidden 阶段。该同步就同步别拿单例当线程安全护身符 对象内部的可变状态该用std::atomic就用原子该用互斥量就用互斥量。magic static 只保证对象创建一次不保证成员函数并发安全。只在真正需要全局唯一时使用 如果一个对象未来可能会被测试替换成 mock、或者可能有多个实例那提前把它设计成普通类用依赖注入管理生命周期以后改造会容易得多。还有一个小技巧想分享带参数的泛型单例第一次传参生效、后续传参被忽略这个行为很容易让调用方困惑。我在模板注释里会明确写“该函数只在首次调用时触发构造后续参数不会更新已有实例”并在代码评审时专门强调这一点。很多看似神秘的线上问题其实都是这种“单例语义没有被充分描述清楚”造成的。C11 的 magic static 让单例实现第一次同时满足了“惰性初始化、线程安全、代码简洁”三个目标而泛型模板又把重复劳动压缩到了最小。珍视这份语法糖但也要记住它背后是一条内存模型层面的硬保证。把该安全的部分交给语言把该谨慎的部分留给自己。

相关新闻

ASP.NET餐饮管理系统开发实战:从数据库设计到部署排错

ASP.NET餐饮管理系统开发实战:从数据库设计到部署排错

简介:这套基于ASP.NET的餐饮管理系统完整项目,面向餐饮行业信息化开发人员、.NET学习者及毕业设计使用者,可帮助理解基于Web的餐饮业务管理流程与分层架构设计。系统以ASP.NET结合SQL Server 2008实现,采用表现层、业务逻辑层、数…

2026/10/14 22:22:48 阅读更多 →
Go数据库NULL扫描报错的类型建模与工程化治理

Go数据库NULL扫描报错的类型建模与工程化治理

1. 问题本质:这不是Bug,是Go类型系统对SQL NULL的刚性拒绝“sql: Scan error on column index 3, name ‘user_id’: converting NULL to int64”——这行报错在Golang后端开发中出现频率极高,尤其在使用sqlx这类轻量级数据库工具时。它不是数…

2026/10/14 22:22:48 阅读更多 →
仿写小米官网:前端工程化实战入门指南

仿写小米官网:前端工程化实战入门指南

1. 为什么“仿写小米官网”是前端新人绕不开的硬核练兵场刚带完一批某高校实训班的学生,有个现象特别明显:凡是认真拆解过小米官网首页结构、手敲过其中核心模块的同学,后续学Vue组件通信、React状态管理时,理解速度明显快一截&am…

2026/10/14 22:22:48 阅读更多 →

最新新闻

用Hermes Agent打造AI数字员工:企业自动化实战全解析

用Hermes Agent打造AI数字员工:企业自动化实战全解析

先交代一下背景,这个教程源自我在某企业内部做的一次 AI 自动化试点总结。当时团队的任务很直接,把重复性高、规则清晰但又需要人肉判断的活儿,逐步交给一套能听指挥、能调用工具、能自己干活的系统。市面上轮子很多,最终敲定用 H…

2026/10/14 23:14:55 阅读更多 →
PyTorch SGD优化器深度解析:动量、weight decay与参数配置实战

PyTorch SGD优化器深度解析:动量、weight decay与参数配置实战

1. 从一个 epoch 的 loss 曲线说起刚接触 PyTorch 那会儿,我写训练脚本基本是抄模板:optimizer torch.optim.SGD(model.parameters(), lr0.01),然后loss.backward()、optimizer.step()、optimizer.zero_grad()三件套走一遍,跑起来…

2026/10/14 23:14:55 阅读更多 →
WebStorm中Vue3+Vitesse项目路径爆红:tsconfig与自动导入声明文件的排查与修复

WebStorm中Vue3+Vitesse项目路径爆红:tsconfig与自动导入声明文件的排查与修复

1. 项目跑得飞起,编辑器却一路飘红:先分辨红线来自谁打开WebStorm,import { useUserStore } from /stores/user下面一条红色波浪线,鼠标移上去是TS2307: Cannot find module /stores/user,可项目在终端里pnpm dev跑得稳…

2026/10/14 23:14:54 阅读更多 →
JSON SyntaxError at position 134 的前端排查与根因分析

JSON SyntaxError at position 134 的前端排查与根因分析

先说让我烦躁的那个瞬间:接口明明返回了 200,网络面板里也看得到数据,前端却红字一片,SyntaxError: Expected , or } after property value in JSON at position 134 (line 1 column 135)。我赌你看到这个报错的第一反应是去翻接口…

2026/10/14 23:14:54 阅读更多 →
Hermes Agent实战:从零搭建AI数字员工实现业务自动化

Hermes Agent实战:从零搭建AI数字员工实现业务自动化

我最早把 Hermes Agent 用进实际业务,是在给一家公司做销售运营自动化的时候。当时团队每天要花两小时人工拉数据、算环比、写简报,我把这套流程拆成几个工具调用和大模型的决策编排,用 Hermes Agent 搭了一个“数字员工”:每天早…

2026/10/14 23:14:54 阅读更多 →
RAG数据预处理:纯Python实现TXT转结构化Markdown,提升检索召回率

RAG数据预处理:纯Python实现TXT转结构化Markdown,提升检索召回率

如果你做过 RAG(检索增强生成)项目,一定遇到过这种窘境:数据拿到手是几百个 txt 文件,没有标题层级,没有段落标记,甚至连编码都不统一。把这种文本丢给 LangChain 或者 LlamaIndex,切…

2026/10/14 23:13:51 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →