展开菜单
首页 精品内容 本月促销 装机必备 Windows PDF教程 macOS软件 IOS软件 Android AI 专题
全部分类

当前位置:

首页 > 编程开发 > C++ 内存序六件套:从完全同步到爱咋咋地

C++ 内存序六件套:从完全同步到爱咋咋地

C++原子操作的内存序从最严格的顺序一致性到最宽松的自由序,不同级别控制着指令重排与可见性。使用恰当可提升性能,选错则破坏数据一致性,需明确每种内存序的适用场景。

本文目录

    我们写了个 store(memory_order_relaxed),CPU 心想“好的老板,我先把后面那行提前执行了,因为它效率高”,然后我们的数据就烂了。

    C++ 内存序六件套:从完全同步到爱咋咋地

    C++ 的这几种内存序用好了起飞,用错了也能起飞。关键在于,得清楚每一把“钥匙”到底能打开哪扇门。

    原子操作与内存序枚举

    1. std::atomic 的常用操作

    单纯的 load / store

    std::atomic<int> flag{ 0 };
    int a = flag.load(); // 读
    flag.store(1); // 写
    

    load 负责原子地获取原子对象的值,store 则是原子地将值替换成非原子参数。它们的内存序默认为 memory_order_seq_cst,也就是最严格、最安全但也是性能开销最大的那个。

    读-改-写全家桶

    • exchange():原子地替换值并返回旧值。可以理解为一个“交换”操作,既能拿回旧信息,又能设定新状态。
    int main()
    {
        std::atomic<int> flag(0);
        int old = flag.exchange(1); // 原子地设为1,返回旧值0
        std::cout << "旧值: " << old << ", 新值: " << flag.load() << std::endl;
        // 输出: 旧值: 0, 新值: 1
    }
    
    • compare_exchange_weak() / compare_exchange_strong():经典的“比较并交换”。只有当前值等于期望值时,才将其替换成新值,否则将当前值写入期望值。返回值指示是否替换成功。

    weak 版本在某些平台上性能更好,但它可能出现“虚假失败”——明明值相等却告诉你不行。因此,它必须在循环中使用,不断重试。strong 版本则保证:只有值真正不同时才会失败,使用起来更直观。

    std::atomic<int> counter(0);void increment()
    {
        int expected = counter.load();
        while (!counter.compare_exchange_weak(expected, expected + 1))
        {
            // 失败时 expected 已被更新为当前值,再次尝试
        }
    }int main()
    {
        std::vector threads;
        for (int i = 0; i < 10; ++i)
            threads.emplace_back(increment);
        for (auto& t : threads)
            t.join();
        std::cout << "最终计数: " << counter << std::endl; // 10
    }
    
    • fetch_add() / fetch_sub():对整数或指针原子地执行加/减操作,并返回旧值。这是实现无锁计数器的核心。
    std::atomic<int> total(0);void add(int n)
    {
        for (int i = 0; i < 1000; ++i)
            total.fetch_add(n);
    }int main()
    {
        std::vector threads;
        for (int i = 0; i < 4; ++i)
            threads.emplace_back(add, 1);
        for (auto& t : threads)
            t.join();
        std::cout << "总和: " << total << std::endl; // 4000
    }
    

    2. 内存序六元组

    C++ 一口气给了我们六个枚举值,按实际能力可以划分成几个层次。

    seq_cst:顺序一致性

    memory_order_seq_cst 是默认选项,也是唯一一个能给所有 atomic 操作建立全局一致修改顺序的选项。简单来说,整个程序里所有原子变量的操作,就像在一个看不见的队列上排队发生。我们能清楚地讲出“A 的 store 在 B 的 load 之前”这种话,而且这个顺序对所有线程可见、一致。

    代价呢?在现代 CPU 上,它经常要插入昂贵的内存栅栏。用它写生产者消费者模型,稳是稳,但性能浪费了一大半,属于用劳斯莱斯的成本干三轮车的活。

    release / acquire / acq_rel:高效同步

    • store(memory_order_release):当前线程在此之前的所有写操作,对于之后对此原子变量做获取读的线程,全部可见。
    • load(memory_order_acquire):当前线程在此之后的所有读/写,不会被重排到这个 load 之前。
    • memory_order_acq_rel 只用于“读-改-写”操作,兼具 acquire 和 release 的效果,是一个“读-改-写全局同步点”。

    relaxed:只有原子性,没有顺序

    memory_order_relaxed 是真正的“没有感情的计数器”。它只保证操作本身是原子的,至于操作在全局顺序里排哪、其他线程能不能看到其他变量的最新值,一概不管。

    它的绝对安全区在哪?就是那些无同步依赖的独立计数器:比如统计访问次数、引用计数的加加减减,只要最终一致,中间漏几个计数也无伤大雅。

    一旦试图用 relaxed 去做标记保护数据,就会喜提“编译器/CPU 随便重排,数据未初始化”的经典体验。

    consume:坑

    memory_order_consume 设计初衷是比 acquire 更轻量,只保证数据依赖关系有序。但现实是,几乎所有编译器都把它当作 acquire 来实现,因为正确实现依赖追踪太难了。标准委员会自己都快放弃了,至今处于不建议使用状态,基本可以当作不存在。

    逐项解析六种内存序

    1. memory_order_seq_cst

    seq_cst 帮我们把所有事情都安排好了,很安全,但后来才发现这份“保护费”有多昂贵。

    它干了什么?所有标记 seq_cst 的操作,在所有线程看来,都遵守同一个全局的、单一的总顺序。不用担心“线程 A 看到的顺序和线程 B 不一样”,所有 seq_cst 操作就像排队,谁先谁后一目了然,而且对所有观察者一致。

    std::atomic<bool> x{false}, y{false};
    std::atomic<int> z{0};void write_x()
    {
        x.store(true, std::memory_order_seq_cst);
    }void write_y()
    {
        y.store(true, std::memory_order_seq_cst);
    }void read_x_then_y()
    {
        while (!x.load(std::memory_order_seq_cst)) {}
        if (y.load(std::memory_order_seq_cst))
            ++z;
    }void read_y_then_x()
    {
        while (!y.load(std::memory_order_seq_cst)) {}
        if (x.load(std::memory_order_seq_cst))
            ++z;
    }int main()
    {
        std::thread t1(write_x), t2(write_y), t3(read_x_then_y), t4(read_y_then_x);
        t1.join(); t2.join(); t3.join(); t4.join();
        assert(z.load() != 0); // 永远不会失败
    }
    

    这个例子是经典的商店-客户条件。用 seq_cst 时 z 永远不会为 0,因为 seq_cst 保证全局一致性:没有任何观察者会看到 x 和 y 都已被置位却漏掉另一个。换成更弱的内存序,这个断言就敢崩给我们看。

    seq_cst 默认省心,但在 x86 上其实也贵。x86 的 load 自带 acquire,store 自带 release,但加上 seq_cst 后每次都得多等一个锁总线,性能可能直接腰斩。

    2. acquire 和 release

    它们不是锁,却比锁更灵活,专门用来在写线程与读线程之间传递“前面那一堆数据都就绪了”的信号。

    • store(memory_order_release):当前线程在此之前的所有写操作,对于之后对此原子变量做获取读的线程,全部可见。
    • load(memory_order_acquire):当前线程在此之后的所有读/写,不会被重排到这个 load 之前。

    有一点必须牢记:release 和 acquire 必须作用于同一个原子变量才能直接建立同步关系。换到不同变量上,就没有这种效果。

    std::atomic<bool> ready{ false };
    std::string data; // 普通共享变量void producer()
    {
        data = "非常非常重要的消息"; // 1
        ready.store(true, std::memory_order_release); // 2、release
    }void consumer()
    {
        while (!ready.load(std::memory_order_acquire)) {} // 3、acquire!
        assert(data == "非常非常重要的消息"); // 4、保证看见 1
    }
    

    当 consumer 看到 ready 为 true 时,它也同时保证看到了 data 的完整写入。这就是经典的 happens-before 链:1 → 2(同线程顺序),2 与 3 之间形成同步,3 → 4(同线程顺序)。没有这个序,大概率会在 data 里读到空字符串或半条乱码。

    3. memory_order_acq_rel

    这是给读-改-写操作(RMW)量身定做的,相当于在同一个操作里既做了 acquire 又做了 release。

    典型场景是:用原子变量实现自旋锁、或节点引用计数调整时,需要同时建立前后因果关系。

    std::atomic<int> sync_point{ 0 };void thread1()
    {
        int old = sync_point.fetch_add(1, std::memory_order_acq_rel);
        // 获取之前的状态 + 释放本次修改给后面的观察者
    }
    

    更具体的一个自旋锁:

    std::atomic<bool> lock_flag{false};void lock()
    {
        while (lock_flag.exchange(true, std::memory_order_acq_rel))
        {
            // 自旋等待
        }
        // 获取锁:acquire 部分确保临界区不会泄漏到加锁前
        // 释放的语义则由之后显式 store(release) 负责,其实用 acquire 就够了,acq_rel 在这里稍重,但无伤大雅
    }
    

    不过说实话,自旋锁的标准写法通常用 acquire 获取,release 释放。而 RMW 里用 acq_rel,主要是为了某些复杂无锁结构,必须一步完成 acquire 和 release,防止中间被插队。

    acq_rel 用起来要斟酌:它比单纯的 release 或 acquire 都重,如果只是要读或只是写,别上头乱套。

    4. memory_order_consume

    consume 的设计初衷是轻量级 acquire:只保证依赖于这个 load 结果的数据依赖关系有序,而不必像 acquire 那样建立一个沉重的栅栏。比如:

    std::atomic ptr;
    std::string data;void producer()
    {
        data = "message";
        ptr.store(&data, std::memory_order_release);
    }void consumer()
    {
        std::string* p = ptr.load(std::memory_order_consume);
        if (p)
        {
            // 因为有依赖,*p 能看到完整的 "message"
            assert(*p == "message");
        }
    }
    

    理论上很美好,利用 CPU 内部的数据依赖顺序,省去一道内存屏障。但问题来了:编译器要精确追踪依赖,太难了。什么 p->length()、把指针放进结构体再转出来、函数调用间接引用……编译器几乎没法在不产生大量保守同步的情况下证明没有依赖。于是,主流编译器直接摆烂,把 consume 实现为完整的 acquire。

    这就导致一个荒诞局面:你写 consume 本来想省性能,结果编译器默默给你加了全套 acquire 的栅栏,还多了语义不一致的风险。标准委员会也基本放弃了,当前明确定位为不鼓励使用。

    5. memory_order_relaxed

    relaxed 只做一个承诺:对该变量的操作本身是原子的,仅此而已。没有 happens-before,没有全局顺序,连编译器重排都放任自由。

    合法用法:不需要任何同步副作用的纯计数器。

    std::atomic<long long> hit_count{0};void increment()
    {
        hit_count.fetch_add(1, std::memory_order_relaxed);
    }
    

    就算两个线程同时加,得到的一定是正确加法结果(原子性),但此时其他变量可能乱七八糟,根本无法推断除了这个计数器本身之外还有什么值。这在统计请求数、错误计数等场景完全够用。

    作死示范:

    std::atomic<bool> valid{ false };
    int value = 0;void threadA()
    {
        value = 21;
        valid.store(true, std::memory_order_relaxed);  // 1、没有 release
    }void threadB()
    {
        if (valid.load(std::memory_order_relaxed)) // 2、没有 acquire
        {   
            // 可能 value 还是 0,因为顺序未定义
            std::cout << value << std::endl;
        }
    }
    

    这段代码在一些 CPU 和编译器眼里,完全可以把 valid.store 提前到 value = 21 之前,把混乱展示得淋漓尽致。

    6. 总结一下

    • 想要安全感,用 seq_cst。
    • 想要性能又要有正确同步,release/acquire 是绝对主力。
    • 只做纯原子运算且不涉及任何数据依赖,relaxed 随便用。
    • acq_rel 留给 RMW 的复合需求。
    • consume?请直接烧纸上香,忘掉它。

    内存序的典型应用模式

    1. 无锁单生产者单消费者队列

    单生产者单消费者意味着不需要处理多写者的复杂竞争,只需要防止生产者和消费者踩同一个地方。

    一个简单的环形缓冲区,用两个 atomic 下标 + relaxed + release/acquire 就能搭起来:

    template <typename T, size_t N>
    class SPSCQueue
    {
        T buffer[N];
        std::atomic<size_t> write_idx{ 0 };
        std::atomic<size_t> read_idx{ 0 };
    public:
        bool try_push(const T& item)
        {
            size_t w = write_idx.load(std::memory_order_relaxed);
            size_t next = (w + 1) % N;
            if (next == read_idx.load(std::memory_order_acquire)) // 获取消费者最新进度
                return false; // 满
            buffer[w] = item; // 1
            write_idx.store(next, std::memory_order_release); // 2 发布元素
            return true;
        }    bool try_pop(T& item)
        {
            size_t r = read_idx.load(std::memory_order_relaxed);
            if (r == write_idx.load(std::memory_order_acquire)) // 获取生产者最新进度
                return false; // 空
            item = buffer[r]; // 3
            read_idx.store((r + 1) % N, std::memory_order_release); // 4 释放槽位
            return true;
        }
    };
    

    这里的要点在于:

    • 生产者用 relaxed 读自己的 write_idx 没问题,因为只有自己在改它。但读 read_idx 必须用 acquire,这样能看到消费者刚释放的槽位确实是空的。
    • 消费者对称:读 write_idx 用 acquire 可以看到生产者刚写入的元素,读自己的 read_idx 用 relaxed。

    如果这里用 seq_cst,会强制额外同步,把这条轻量队列的性能优势吃掉一大半。

    2. 双层检查锁定的实现

    C++ 史上最臭名昭著的反模式之一,直到 atomic 和正确的内存序入场才得以洗净冤屈。

    需求:懒汉单例,首次构造后无锁访问。

    早期作死写法:

    if (!instance) // 非原子读取,数据竞争!
    {
        lock_guard lock(mtx);
        if (!instance) instance = new Foo; // 普通指针,可能指令重排
    }
    

    这等于把未定义拌上数据竞争再浇一勺指令重排,味道相当“醇厚”。

    现在使用原子 + acquire/release:

    class Foo
    {
    public:
        void doSomething() { /* 实际业务逻辑 */ }private:
        Foo() = default;    // 禁止拷贝和移动,保证单例唯一性
        Foo(const Foo&) = delete;
        Foo& operator=(const Foo&) = delete;    // 设为友元,使其能访问私有构造函数
        friend Foo* getFoo();
    };std::atomic instance{ nullptr };
    std::mutex mtx;Foo* getFoo()
    {
        Foo* tmp = instance.load(std::memory_order_acquire); // 1
        if (!tmp)
        {
            std::lock_guard lock(mtx);
            tmp = instance.load(std::memory_order_relaxed); // 2 持锁后再看一次
            if (!tmp)
            {
                tmp = new Foo();
                instance.store(tmp, std::memory_order_release); // 3
            }
        }
        return tmp;
    }
    

    这个方案里:

    • 1 用 acquire:一旦看见非空指针,保证能看到对象的完整构造。
    • 2 用 relaxed:在锁内第二次读取,没有竞争,不需要同步。
    • 3 用 release:对象构造完毕,发布给后续所有 acquire 读操作。

    3. 发布一系列对象(发布-订阅)

    这个模式本质就是 release / acquire 的扩大版本:生产的不止一个变量,而是一整包数据,然后一把发布出去;消费者用自己的订阅动作一手接住全部数据。

    一个典型例子:多线程日志后端,主线程把一帧完整日志数据填好,然后原子地发布指针,日志消费者看到指针就安全消费。

    struct LogBlock
    {
        char data[256];
        size_t len;
    };std::atomic latest_log{ nullptr };void producer(const char* msg)
    {
        auto* block = new LogBlock();
        strncpy(block->data, msg, sizeof(block->data));
        block->len = strlen(msg);
        // 所有数据准备好,一次性发布
        latest_log.store(block, std::memory_order_release);
    }void consumer()
    {
        while (true)
        {
            LogBlock* block = latest_log.load(std::memory_order_acquire);
            if (block)
            {
                // 拿到指针后,由于 acquire,看到的是完整的 block 内容
                write_to_disk(block->data, block->len);
                // 为了简化没回收内存
            }
        }
    }
    

    消息里所有普通成员(data、len)的写入,保证了在 store(release) 之前发生;消费者的 load(acquire) 则保证在这些成员的使用之前发生。这就形成了一个非常干净的同步链,不需要任何锁,发布者立刻回去干活,订阅者按自己的步调消费。

    这类场景要是不小心写成了 relaxed,就会喜提:数据不定时缺失、偶尔半条消息。

    结尾

    所以最后就一句话:std::atomic 就是一份使用说明书,内存序也不是玄学,是我们欠 CPU 的一份礼貌。忽略它,程序会跑,但跑着跑着容易摔进坑里。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    相关文章 更多
    精品专题 更多
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    macOS软件

    正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

    IOS软件

    正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

    AI

    正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

    Mac软件 更多
    AirBuddy 3 macOS版
    AirBuddy 3 macOS版

    AirBuddy 3 是面向 macOS 的蓝牙外设增强工具,可集中查看 AirPods、Beats 与苹果设备电量,提供快速连接、播放控制、电量提醒、桌面小组件和多台 Mac 外设接力等功能。

    AirServer macOS版
    AirServer macOS版

    AirServer macOS版可把 Mac 转换为无线投屏接收端,通过 AirPlay 或 Google Cast 接收手机、平板和电脑画面,并支持多设备同时连接、高清镜像及会话录制,适合家庭娱乐、课堂教学、会议演示和应用展示。

    AnyDesk macOS版
    AnyDesk macOS版

    AnyDesk macOS版是一款适用于远程办公、设备维护和客户支持的远程桌面工具,可连接并操作异地计算机,支持文件传输、无人值守访问、会话权限控制及跨平台协作。

    Beamer 4 macOS版
    Beamer 4 macOS版

    Beamer 4是面向macOS的本地视频投屏工具,可将Mac中的影片投放到Apple TV、Chromecast及兼容设备,支持常见格式、多类字幕、环绕声、播放列表和电视遥控操作。

    Boom 3D macOS版
    Boom 3D macOS版

    Boom 3D macOS版是一款系统级音效增强工具,可为音乐、电影、游戏和流媒体提供3D虚拟环绕声、31段均衡器、音量增强及应用独立音量控制,并支持本地播放器与多种音频输出设备。

    Boris FX macOS版
    Boris FX macOS版

    Boris FX macOS版是一套面向剪辑、合成、调色及动态图形工作的专业后期工具,涵盖视觉特效、平面与三维跟踪、转描、画面修复、粒子动画和音频清理,可配合多种主流宿主软件使用。

    Capto macOS版
    Capto macOS版

    Capto 是面向 macOS 的屏幕截图、高清录屏与媒体编辑工具,支持区域及整屏捕获、网页长截图、双音轨处理、图片和视频标注、素材分类与多渠道分享,适合教师、学生、内容创作者及需要制作演示教程的用户。

    ChemDraw macOS版
    ChemDraw macOS版

    ChemDraw macOS版是一款面向化学学习、科研和制药工作的专业结构绘图工具,可创建分子、反应及生物聚合物图形,并提供结构整理、格式转换和科研成果展示能力。

    Cursor Crane macOS版
    Cursor Crane macOS版

    Cursor Crane 是一款仅面向 macOS 的键盘操控效率工具,可通过提示标签、网格、文字查找和窗口模式完成点击、滚动、定位与切换,并支持屏幕内容提取、脚本及工作流扩展。

    Deep Freeze 冰点还原 macOS版
    Deep Freeze 冰点还原 macOS版

    Deep Freeze 冰点还原 macOS版通过冻结系统配置,让受保护的Mac在重新启动后回到预设状态,并提供解冻空间、维护计划、密码保护及Apple Remote Desktop管理能力。

    Focusky macOS版
    Focusky macOS版

    Focusky macOS版是一款面向教学课件、商务汇报、产品展示和动画宣传内容的演示制作工具。它通过无限画布、路径编排、缩放旋转转场及多媒体编辑,将传统逐页幻灯片转化为更具空间感的动态演示。

    Folder Colorizer 2 macOS版
    Folder Colorizer 2 macOS版

    Folder Colorizer 2 macOS版是一款直观的文件夹外观定制工具,可更换颜色,添加表情、贴纸及图片背景,并支持批量处理、智能推荐和恢复默认样式,帮助用户更快辨认工作、学习与生活资料。

    WINDOWS 更多
    讯飞绘文 网页版
    讯飞绘文 网页版

    讯飞绘文网页版是科大讯飞推出的AI图文创作与内容运营工具,覆盖热点选题、文章生成、稿件校对、智能配图、排版发布和数据复盘等环节,适合自媒体创作者及内容团队在线使用。

    AirServer Windows版
    AirServer Windows版

    AirServer Windows版可把电脑变成AirPlay、Google Cast和Miracast无线投屏接收端,适合会议演示、课堂教学及家庭大屏分享。发送设备通常无需另装客户端,并可同时接收和智能排列多个镜像会话。

    AlwaysUp Windows版
    AlwaysUp Windows版

    AlwaysUp是一款面向Windows的系统服务注册与程序托管工具,可让普通应用、脚本及批处理在无人登录时随系统启动,并通过状态检测、异常重启、资源监控和邮件通知保障关键任务持续运行。

    AnyDesk Windows版
    AnyDesk Windows版

    AnyDesk Windows版是一款轻量化远程桌面与技术支持工具,可用于控制异地电脑、传输文件、远程打印及无人值守访问。它兼顾连接响应、安全权限和部署灵活性,适合个人远程办公、企业设备维护与客服支持。

    Boom 3D Windows版
    Boom 3D Windows版

    Boom 3D Windows版是一款面向音乐、电影和游戏场景的系统级音效增强工具,支持虚拟3D环绕声、31段均衡器、预设音效、应用音量控制及本地音乐播放,可配合耳机、扬声器和蓝牙耳机使用。

    Boris FX Windows版
    Boris FX Windows版

    Boris FX Windows版是一套服务于影视剪辑、视觉特效、运动跟踪、转描、图像修饰及音频修复的专业创作工具集合,可作为插件接入多种主流后期宿主,也包含可独立运行的应用,适合剪辑师、特效师与内容创作者使用。

    ChemDraw Windows版
    ChemDraw Windows版

    ChemDraw Windows版是一款面向化学研究、药物研发、材料科学和教学工作的专业结构绘图工具,可快速绘制分子、反应式及生物聚合物,并提供结构整理、格式转换、性质计算和科研文档协作能力。

    CODESOFT Windows版
    CODESOFT Windows版

    CODESOFT Windows版是一套企业级条码与RFID标签设计打印工具,可连接Excel、Access、SQL及云端数据源,支持变量、表单、GS1条码、数据库记录和多种工业打印机,适合制造、仓储、物流及合规标识场景。

    Deep Freeze 冰点还原 Windows版
    Deep Freeze 冰点还原 Windows版

    Deep Freeze 冰点还原 Windows版通过重启恢复技术保护系统盘及指定分区,自动撤销恶意程序、误操作和配置漂移造成的改动,并支持密码保护、解冻空间与多硬盘管理,适合学校机房、公共电脑、办公终端及家庭电脑使用。

    EDIUS Windows版
    EDIUS Windows版

    EDIUS 11 Windows版是一款面向影视制作、广播电视、纪录片、短视频及教育机构的专业非线性编辑工具,支持4K/8K、多格式混编、HDR调色、字幕、音频处理和后台渲染,并提供素材管理及丰富的导出预设。

    FastStone Capture Windows版
    FastStone Capture Windows版

    FastStone Capture Windows版是一款轻量级屏幕捕捉、录屏与图像编辑工具,可截取窗口、区域、菜单和滚动页面,并提供标注、模糊、取色、OCR、录音录像及多格式导出能力,适合办公说明、教程制作和问题反馈。

    Focusky Windows版
    Focusky Windows版

    Focusky Windows版是一款动画演示与交互课件制作工具,通过无限画布、缩放旋转镜头、模板素材和对象动画组织内容,可导入PPTX,并输出视频、EXE、PDF、网页等格式,适用于课堂教学、微课、商务汇报及产品展示。