第12章:优化并发_《C++性能优化指南》notes

news2025/3/31 1:52:02

优化并发

      • 一、并发基础与优化核心知识点
      • 二、关键代码示例与测试
      • 三、关键优化策略总结
      • 四、性能测试方法论
      • 多选题
      • 设计题
      • 答案与详解
        • 多选题答案:
      • 设计题答案示例

一、并发基础与优化核心知识点

  1. 线程 vs 异步任务
  • 核心区别std::thread直接管理线程,std::async由运行时决定异步策略(可能用线程池)。
  • 优化点:频繁创建线程开销大,优先用 std::async
  1. 原子操作与内存序
  • 原子类型std::atomic<T>确保操作不可分割。
  • 内存序memory_order_relaxed(无同步)到 memory_order_seq_cst(全序同步)。
  1. 锁的精细控制
  • 锁类型std::mutexstd::recursive_mutexstd::shared_mutex
  • 优化技巧:缩短临界区、避免嵌套锁、用 std::lock_guard/std::unique_lock自动管理。
  1. 条件变量与生产者-消费者
  • 使用模式wait()搭配谓词防止虚假唤醒,notify_one()/notify_all()通知。
  1. 无锁数据结构
  • 适用场景:高并发计数器、队列等,减少锁竞争。

二、关键代码示例与测试

示例1:原子操作 vs 互斥锁的性能对比

#include <iostream>
#include <atomic>
#include <mutex>
#include <thread>
#include <vector>
#include <chrono>

constexpr int kIncrements = 1'000'000;

// 使用互斥锁的计数器
struct MutexCounter {
    int value = 0;
    std::mutex mtx;
    void increment() {
        std::lock_guard<std::mutex> lock(mtx);
        ++value;
    }
};

// 使用原子操作的计数器
struct AtomicCounter {
    std::atomic<int> value{0};
    void increment() {
        value.fetch_add(1, std::memory_order_relaxed);
    }
};

template<typename Counter>
void test_performance(const std::string& name) {
    Counter counter;
    auto start = std::chrono::high_resolution_clock::now();
  
    std::vector<std::thread> threads;
    for (int i = 0; i < 4; ++i) {
        threads.emplace_back([&counter] {
            for (int j = 0; j < kIncrements; ++j) {
                counter.increment();
            }
        });
    }
  
    for (auto& t : threads) t.join();
  
    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
  
    std::cout << name << " Result: " << counter.value 
              << ", Time: " << duration << "ms\n";
}

int main() {
    test_performance<MutexCounter>("Mutex Counter");
    test_performance<AtomicCounter>("Atomic Counter");
    return 0;
}

编译命令

g++ -std=c++11 -O2 -pthread atomic_vs_mutex.cpp -o atomic_vs_mutex

输出示例

Mutex Counter Result: 4000000, Time: 182ms
Atomic Counter Result: 4000000, Time: 32ms

结论:原子操作在高并发下性能显著优于互斥锁。


示例2:线程池实现(减少线程创建开销)

#include <iostream>
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>

class ThreadPool {
public:
    ThreadPool(size_t num_threads) : stop(false) {
        for (size_t i = 0; i < num_threads; ++i) {
            workers.emplace_back([this] {
                while (true) {
                    std::function<void()> task;
                    {
                        std::unique_lock<std::mutex> lock(queue_mutex);
                        condition.wait(lock, [this] { 
                            return stop || !tasks.empty(); 
                        });
                        if (stop && tasks.empty()) return;
                        task = std::move(tasks.front());
                        tasks.pop();
                    }
                    task();
                }
            });
        }
    }
  
    template<class F, class... Args>
    auto enqueue(F&& f, Args&&... args) -> std::future<decltype(f(args...))> {
        using return_type = decltype(f(args...));
        auto task = std::make_shared<std::packaged_task<return_type()>>(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        );
      
        std::future<return_type> res = task->get_future();
        {
            std::unique_lock<std::mutex> lock(queue_mutex);
            tasks.emplace([task] { (*task)(); });
        }
        condition.notify_one();
        return res;
    }
  
    ~ThreadPool() {
        {
            std::unique_lock<std::mutex> lock(queue_mutex);
            stop = true;
        }
        condition.notify_all();
        for (std::thread& worker : workers)
            worker.join();
    }

private:
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;
    std::mutex queue_mutex;
    std::condition_variable condition;
    bool stop;
};

int main() {
    ThreadPool pool(4);
    std::vector<std::future<int>> results;
  
    for (int i = 0; i < 8; ++i) {
        results.emplace_back(
            pool.enqueue([i] {
                std::cout << "Task " << i << " executed\n";
                return i * i;
            })
        );
    }
  
    for (auto& result : results)
        std::cout << "Result: " << result.get() << std::endl;
      
    return 0;
}

编译命令

g++ -std=c++11 -O2 -pthread thread_pool.cpp -o thread_pool

输出示例

Task Result: Task 3Task 1Task 20 executed
 executed
Task 5 executed
Task 40 executed
Task 7Task 6 executed
 executed
 executed
 executed

Result: 1
Result: 4
Result: 9
Result: 16
Result: 25
Result: 36
Result: 49

结论:线程池复用线程,减少频繁创建销毁的开销。


示例3:使用无锁队列提升并发

#include <iostream>
#include <atomic>
#include <thread>

template<typename T>
class LockFreeQueue {
public:
    struct Node {
        T data;
        Node* next;
        Node(const T& data) : data(data), next(nullptr) {}
    };

    LockFreeQueue() : head(new Node(T())), tail(head.load()) {}
  
    void push(const T& data) {
        Node* newNode = new Node(data);
        Node* prevTail = tail.exchange(newNode);
        prevTail->next = newNode;
    }
  
    bool pop(T& result) {
        Node* oldHead = head.load();
        if (oldHead == tail.load()) return false;
        head.store(oldHead->next);
        result = oldHead->next->data;
        delete oldHead;
        return true;
    }

private:
    std::atomic<Node*> head;
    std::atomic<Node*> tail;
};

int main() {
    LockFreeQueue<int> queue;
  
    // 生产者线程
    std::thread producer([&] {
        for (int i = 0; i < 10; ++i) {
            queue.push(i);
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    });
  
    // 消费者线程
    std::thread consumer([&] {
        int value;
        while (true) {
            if (queue.pop(value)) {
                std::cout << "Consumed: " << value << std::endl;
                if (value == 9) break;
            }
        }
    });
  
    producer.join();
    consumer.join();
    return 0;
}

编译命令

g++ -std=c++11 -O2 -pthread lockfree_queue.cpp -o lockfree_queue

输出示例

Consumed: 0
Consumed: 1
Consumed: 2
Consumed: 3
Consumed: 4
Consumed: 5
Consumed: 6
Consumed: 7
Consumed: 8
Consumed: 9

结论:无锁队列减少锁争用,适合高并发场景。


三、关键优化策略总结

  1. 减少锁竞争

    • 缩小临界区范围。
    • 使用读写锁(std::shared_mutex)区分读写操作。
  2. 利用原子操作

    • 简单计数器优先用 std::atomic
    • 适当选择内存序(如 memory_order_relaxed)。
  3. 异步与线程池

    • 避免频繁创建线程,使用 std::async或自定义线程池。
  4. 无锁数据结构

    • 在CAS(Compare-And-Swap)安全时使用,但需注意ABA问题。
  5. 缓存友好设计

    • 避免false sharing(用 alignas对齐或填充字节)。

四、性能测试方法论

  1. 基准测试

    • 使用 std::chrono精确测量代码段耗时。
    • 对比不同实现的吞吐量(如ops/sec)。
  2. 压力测试

    • 模拟高并发场景,观察资源竞争情况。
  3. 工具辅助

    • Valgrind检测内存问题。
    • perf分析CPU缓存命中率。

多选题

  1. 下列哪些情况可能导致数据竞争?
    A. 多个线程同时读取同一变量
    B. 一个线程写,另一个线程读同一变量
    C. 使用互斥量保护共享变量
    D. 使用原子变量操作

  2. 关于std::asyncstd::thread的选择,正确的说法是?
    A. std::async默认启动策略是延迟执行
    B. std::thread更适合需要直接控制线程生命周期的场景
    C. std::async会自动管理线程池
    D. std::thread无法返回计算结果

  3. 以下哪些操作可能引发死锁?
    A. 在持有锁时调用外部未知代码
    B. 对多个互斥量使用固定顺序加锁
    C. 递归互斥量(std::recursive_mutex)的嵌套加锁
    D. 未及时释放条件变量关联的锁

  4. 关于原子操作的内存顺序,正确的说法是?
    A. memory_order_relaxed不保证操作顺序
    B. memory_order_seq_cst会降低性能但保证全局顺序
    C. memory_order_acquire仅保证读操作的可见性
    D. 原子操作必须与volatile关键字结合使用

  5. 优化同步的合理手段包括:
    A. 将大临界区拆分为多个小临界区
    B. 使用无锁数据结构替代互斥量
    C. 通过std::future传递计算结果
    D. 在单核系统上使用忙等待(busy-wait)

  6. 关于条件变量的正确使用方式:
    A. 必须在循环中检查谓词
    B. notify_one()notify_all()更高效
    C. 可以脱离互斥量单独使用
    D. 虚假唤醒(spurious wakeup)是必须处理的

  7. 以下哪些是"锁护送"(Lock Convoy)的表现?
    A. 多个线程频繁争抢同一互斥量
    B. 线程因锁竞争频繁切换上下文
    C. 互斥量的持有时间极短
    D. 使用读写锁(std::shared_mutex

  8. 减少共享数据竞争的方法包括:
    A. 使用线程局部存储(TLS)
    B. 复制数据到各线程独立处理
    C. 通过消息队列传递数据
    D. 增加互斥量的数量

  9. 关于std::promisestd::future的正确说法是:
    A. std::promise只能设置一次值
    B. std::futureget()会阻塞直到结果就绪
    C. 多个线程可以共享同一个std::future对象
    D. std::async返回的std::future可能延迟执行

  10. 关于原子变量与互斥量的对比,正确的说法是:
    A. 原子变量适用于简单数据类型
    B. 互斥量能保护复杂操作序列
    C. 原子变量的fetch_add是原子的
    D. 互斥量比原子变量性能更好


设计题

  1. 实现一个线程安全的无锁(lock-free)队列
    要求:
  • 使用原子操作实现pushpop
  • 处理ABA问题
  • 提供测试代码验证并发操作正确性
  1. 设计一个生产者-消费者模型
    要求:
  • 使用std::condition_variablestd::mutex
  • 队列长度限制为固定大小
  • 支持多生产者和多消费者
  • 提供测试代码模拟并发场景
  1. 实现基于std::async的并行任务执行器
    要求:
  • 支持提交任意可调用对象
  • 自动回收已完成的任务资源
  • 限制最大并发线程数为CPU核心数
  • 测试代码展示并行加速效果
  1. 优化高竞争场景下的计数器
    要求:
  • 使用线程局部存储(TLS)分散写操作
  • 定期合并局部计数到全局变量
  • 对比普通原子计数器与优化版本的性能差异
  • 提供测试代码和性能统计输出

5 实现一个读写锁(Read-Write Lock)
要求:

  • 支持多个读者或单个写者
  • 避免写者饥饿(writer starvation)
  • 基于std::mutexstd::condition_variable实现
  • 测试代码验证读写操作的互斥性

答案与详解


多选题答案:
  1. B
    解析:数据竞争的条件是至少一个线程写且无同步措施。A只有读不冲突,C/D有同步机制。

  2. B, D
    解析std::async默认策略非延迟(C++11起为std::launch::async|deferred),B正确,D因std::thread无直接返回值机制正确。

  3. A, C
    解析:A可能导致回调中再次加锁;C递归锁允许同一线程重复加锁但需对应解锁次数,误用仍可能死锁。

  4. A, B
    解析memory_order_relaxed无顺序保证,B正确,C中acquire保证后续读的可见性,D错误(原子操作无需volatile)。

  5. A, B, C
    解析:D在单核忙等待浪费CPU,其余均为有效优化手段。

  6. A, B, D
    解析:C错误,条件变量必须与互斥量配合使用。

  7. A, B
    解析:锁护送表现为频繁争抢导致线程切换,C短持有时间反而减少竞争,D无关。

  8. A, B, C
    解析:D增加锁数量可能加剧竞争,其余均为减少竞争的有效方法。

  9. A, B, D
    解析:C错误,std::future不可多线程同时调用get()

  10. A, B, C
    解析:D错误,互斥量在低竞争时性能可能更差。


设计题答案示例

  1. 无锁队列实现(部分代码)
#include <atomic>
#include <memory>

template<typename T>
class LockFreeQueue {
private:
    struct Node {
        std::shared_ptr<T> data;
        std::atomic<Node*> next;
        Node() : next(nullptr) {}
    };

    std::atomic<Node*> head;
    std::atomic<Node*> tail;

public:
    LockFreeQueue() : head(new Node), tail(head.load()) {}

    void push(T value) {
        Node* new_node = new Node;
        new_node->data = std::make_shared<T>(std::move(value));
        Node* old_tail = tail.load();
        while (!old_tail->next.compare_exchange_weak(nullptr, new_node)) {
            old_tail = tail.load();
        }
        tail.compare_exchange_weak(old_tail, new_node);
    }

    std::shared_ptr<T> pop() {
        Node* old_head = head.load();
        while (old_head != tail.load()) {
            if (head.compare_exchange_weak(old_head, old_head->next)) {
                std::shared_ptr<T> res = old_head->next->data;
                delete old_head;
                return res;
            }
        }
        return nullptr;
    }
};

// 测试代码
int main() {
    LockFreeQueue<int> queue;
    queue.push(42);
    auto val = queue.pop();
    if (val && *val == 42) {
        std::cout << "Test passed!\n";
    }
    return 0;
}
  1. 生产者-消费者模型
#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <iostream>

template<typename T>
class SafeQueue {
private:
    std::queue<T> queue;
    std::mutex mtx;
    std::condition_variable cv;
    size_t max_size;

public:
    SafeQueue(size_t size) : max_size(size) {}

    void push(T item) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [this] { return queue.size() < max_size; });
        queue.push(std::move(item));
        cv.notify_all();
    }

    T pop() {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [this] { return !queue.empty(); });
        T val = std::move(queue.front());
        queue.pop();
        cv.notify_all();
        return val;
    }
};

// 测试代码
int main() {
    SafeQueue<int> q(10);
    std::thread producer([&q] {
        for (int i = 0; i < 10; ++i) {
            q.push(i);
        }
    });
    std::thread consumer([&q] {
        for (int i = 0; i < 10; ++i) {
            int val = q.pop();
            std::cout << "Got: " << val << '\n';
        }
    });
    producer.join();
    consumer.join();
    return 0;
}

其他设计题目的答案, 稍后补充

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

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

相关文章

逼用户升级Win11,微软开始给Win10限速

随着Windows10的支持时间越来越短&#xff0c;微软也加大了对Win10用户的驱赶力度。 最近&#xff0c;微软官宣了将要在今年6月份降低OneNote for Windows 10的同步速度。软件也将和Windows10在今年的10月14日一同停止支持和维护。 这将影响实时协作和多设备访问。 对OneNote…

HarmonyOs-ArkUI List组件

列表是一个复杂的容器&#xff0c;当列表项达到一定数量&#xff0c;使得列表内容超出其范围的时候&#xff0c;就会自动变为可以滚动。列表适合用来展现同类数据类型。 List组件支持使用&#xff0c;条件渲染&#xff0c;循环渲染&#xff0c;懒加载等渲染控制方式生成子组件…

基于YOLOv8深度学习的PCB缺陷检测识别系统【python源码+GUI界面+数据集+训练代码+登录界面】

目录 一、界面全貌展示 二、前言摘要 三、GUI界面演示 &#xff08;一&#xff09;用户加载自定义模型 &#xff08;二&#xff09;单张图像检测 &#xff08;三&#xff09;检测图像文件夹 &#xff08;四&#xff09;检测视频 &#xff08;五&#xff09;摄像头检测 …

鸿蒙生态圈暗战:数字孪生三强争霸谁将主宰消费电子未来?

IDC数据显示&#xff0c;2025年Q1华为以38.7%份额领跑中国折叠屏市场&#xff0c;Pura X首月销量突破120万台。这款搭载HarmonyOS 5的旗舰&#xff0c;通过灵犀通信技术实现5G A网络下载速率提升30%&#xff0c;并在离线环境下完成厘米级导航。其爆款逻辑背后&#xff0c;是鸿蒙…

react 15-16-17-18各版本的核心区别、底层原理及演进逻辑的深度解析

一、React 15&#xff08;2016&#xff09; 核心架构&#xff1a;Stack Reconciler&#xff08;栈协调器&#xff09; 工作原理&#xff1a; 同步递归渲染&#xff1a;采用深度优先遍历方式递归处理 Virtual DOM&#xff0c;形成不可中断的调用栈渲染流程&#xff1a;1. 触发 …

CMS迁移中SEO优化整合步骤详解

内容概要 在CMS迁移过程中&#xff0c;系统化的规划与执行是保障SEO排名稳定性的核心。首先需明确迁移流程的关键阶段&#xff0c;包括数据备份、URL适配、元数据同步及安全配置等环节。其中&#xff0c;数据备份不仅需覆盖原始数据库与静态资源&#xff0c;还需验证备份文件的…

数据结构初阶-二叉树链式

目录 1.概念与结构 2.二叉数链式的实现 2.1遍历规则 2.2申请内存空间 2.3手动构建一棵二叉树 2.4二叉树结点的个数 2.5二叉树叶子结点的个数 2.6二叉树第K层结点个数 2.7二叉树的高度 2.8二叉树中查找值为x的结点 2.9二叉树的销毁 3.层序遍历 3.1概念 3.2层序遍历…

Springboot 集成 Flowable 6.8.0

1. 创建 Spring Boot 项目 通过 Spring Initializr&#xff08;https://start.spring.io/ &#xff09;创建一个基础的 Spring Boot 项目&#xff0c;添加以下依赖&#xff1a; Spring WebSpring Data JPAMySQL DriverLombok&#xff08;可选&#xff0c;用于简化代码&#x…

协作机械臂需要加安全墙吗? 安全墙 光栅 干涉区

安全墙是什么 文章目录 安全墙是什么简介1. 物理安全墙1.1 定义&#xff1a;1.2 作用机制&#xff1a;1.3 应用场景&#xff1a; 2. 虚拟安全墙2.2 定义&#xff1a;2.3 作用机制&#xff1a;2.3 应用场景&#xff1a; 3. 安全毛毯3.1 工作原理&#xff1a;3.2 特点3.3 应用场景…

springboot+mybatisplus

1.什么是springboot? Spring Boot是一个用于快速构建Spring应用程序的框架。它旨在帮助开发人员快速搭建Spring框架,减少配置和繁琐的工作。Spring Boot继承了原有Spring框架的优秀基因,使Spring在使用中更加方便快捷。 在Spring Boot中集成ActiveMQ,需要导入相应的starter…

《TypeScript 面试八股:高频考点与核心知识点详解》

“你好啊&#xff01;能把那天没唱的歌再唱给我听吗&#xff1f; ” 前言 因为主包还是主要学习js&#xff0c;ts浅浅的学习了一下&#xff0c;在简历中我也只会写了解&#xff0c;所以我写一些比较基础的八股&#xff0c;如果是想要更深入的八股的话还是建议找别人的。 Ts基…

SICAR 标准 KUKA 机器人标准功能块说明手册

功能块名称:LSicar_Robot_KUKA_PrD 目录 1. 概述 2. 功能说明 2.1 程序控制 2.2 状态监控 2.3 报警与故障处理 2.4 驱动控制 3. 关键参数说明 4. 操作步骤指南 4.1 初始化配置 4.2 运行控制 4.3 状态监控 5. 常见故障处理 6. 注意事项 附录1:程序段索引 附录…

linux中如何修改文件的权限和拥有者所属组

目录标题 chmod指令八进制形式权限修改文件拥有者所属组的修改umask有关内容 chmod指令 chmod指令可以用来修改人员的权限其形式如下&#xff1a; u代表的是拥有者&#xff0c;g代表的是所属组&#xff0c;o代表的是其他人&#xff0c;a表示所有人&#xff0c;如果你想增加权…

掌握Linux项目自动化构建:从零入门make与Makefile

文章目录 前言&#xff1a; 一、初识自动化构建工具1.1 什么是make/Makefile&#xff1f;1.2 快速体验 二、深入理解核心机制2.1 依赖关系与依赖方法2.2 伪目标的妙用2.3 具体语法a.makefile的基本雏形b.makefile推导原则&#xff01; 三、更加具有通用型的makefile1. 变量定义…

Jenkins 配置python项目和allure

Jenkins新建项目 新建ry-api-auto-test。 添加项目描述&#xff0c;选择gitee令牌。 源码管理&#xff0c;设置仓库地址和凭证。参考我上一篇文章的链接&#xff1a;配置gitee私人令牌和凭证 构建步骤&#xff0c;因为我Jenkins部署在Windows&#xff0c;因此选择batch。…

vue3:十一、主页面布局(进入指定菜单页面,默认锁定到左侧菜单)

一、效果 直接进入home页面&#xff0c;直接展开对应的菜单项 二、具体实现 1、菜单容器增加默认选中变量 在菜单容器中将默认展开菜单default-openeds修改为默认选中菜单default-active 2、引入useRoute方法 引入该方法为了获取当前页面的路径 import { useRoute } from …

【蓝桥杯】每日练习 Day13

前言 今天做了不少题&#xff0c;但是感觉都太水了&#xff0c;深思熟虑之下主播决定拿出两道相对不那么水的题来说一下&#xff08;其实还是很水&#xff09;。 两道问题&#xff0c;一道是日期问题&#xff08;模拟&#xff09;&#xff0c;一道是区间合并问题。 日期差值 …

【Docker系列七】Docker Compose 命令详解

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【AI学习】Transformer 模型

1,概念 是一种基于自注意力机制(Self-Attention Mechanism)的深度学习架构,在自然语言处理、计算机视觉等多个领域都有着极为重要的应用。 2,基本结构 1)编码器(Encoder) 通常由多个相同的编码器层堆叠而成。 每个编码器层包含了多头自注意力机制、前馈神经网络以及…

大数据学习栈记——HBase操作(shell java)

本文介绍HBase在shell终端的常见操作以及如何利用java api操作HBase&#xff0c;操作系统&#xff1a;Ubuntu24.04 参考&#xff1a; https://blog.51cto.com/u_16099228/8016429 https://blog.csdn.net/m0_37739193/article/details/73618899 https://cloud.tencent.com/d…