新余专业的企业网站建设公司彩妆网站建设
2026/4/18 5:41:17 网站建设 项目流程
新余专业的企业网站建设公司,彩妆网站建设,资讯网站模版,app成本一、无锁队列 1.1、有锁队列和无锁队列 有锁队列#xff1a;通过互斥锁或其他同步机制保证线程安全的队列#xff0c;属于阻塞队列无锁队列#xff1a;通过原子操作实现线程安全的队列#xff0c;属于非阻塞队列 1.2、锁的局限 线程阻塞带来的上下文切换开销死锁风险性能瓶…一、无锁队列1.1、有锁队列和无锁队列有锁队列通过互斥锁或其他同步机制保证线程安全的队列属于阻塞队列无锁队列通过原子操作实现线程安全的队列属于非阻塞队列1.2、锁的局限线程阻塞带来的上下文切换开销死锁风险性能瓶颈高并发下锁竞争激烈吞吐量下降1.3、非阻塞队列的层次无阻塞(Obstruction-free):系统整体不会因为任何线程挂起而阻塞可能有个别线程需要重试是最弱的非阻塞保证无锁(lock-free)至少一个线程成功其他可能重试依赖cas等原子操作无等待(wait-free)所有线程都能成功无重试依赖exchange等原子操作1.4、设计队列队列一般涉及到生产者和消费者需要根据生产者和消费者的个数来设计队列单生产者单消费者SPSC最简单的无锁队列一般使用acquirerelease内存序即可性能最高多生产者单消费者MPSC多个生产者竞争写入一个消费者读取单生产者多消费者SPMC一个生产者竞争写入多个消费者读取多生产者多消费者MPMC多个生产者竞争写入多个消费者读取1.5、理解volatile内存屏障原子操作之间的关系volatile防止编译器优化确保每次访问都从内存读取/写入不保证内存可见性不提供多线程间的内存可见性保证适用场景硬件寄存器访问(内存映射IO)信号处理setjmp/longjmp场景不适用场景多线程同步(应该适用std::atomic)内存屏障(std::atomic_thread_fence):解决可见性(同步性)与顺序性防止指令重排序保证内存可见性标准库std::atomic_thread_fenceLoad-Load屏障确保屏障前的读操作先于屏障后的读操作Store-Store屏障确保屏障前的写操作先于屏障后的写操作Load-Store屏障确保屏障前的读操作先于屏障后的写操作Store-Load屏障确保屏障前的写操作先于屏障后的读操作原子操作提供不可分割的操作确保多线程安全访问标准库std::atomic内存序:memory_order_relaxed不保证内存可见性和顺序性只保证原子性memory_order_acquire: 本线程后续的读操作不会被重排到此操作之前memory_order_release: 本线程之前的写操作不会被重排到此操作之后memory_order_acq_rel: 本线程之前的写操作不会被重排到此操作之后本线程后续的读操作不会被重排到此操作之前memory_order_seq_cst: 全局顺序保证内存可见性和顺序性但性能较低三者之间的关系volatile≠ 原子操作 ≠ 内存屏障多线程同步的正确组合std::atomic 合适的内存序volatile在多线程中几乎从不用于同步目的1.6、无锁队列(lock-free)设计ringbuffer环形缓冲区数据结构可以使用数组基本实现固定大小的数组 头尾索引(head与tail)push操作移动tail指针pop操作移动head指针缺点需要为最坏的情况预留空间浪费内存可以使用链表可以进行动态伸缩混合队列(hybrid)即固定大小的小数组的链表结合两者优点负责度最高操作push进行写操作需要进行判满pop进行读操作需要进行判空队列需要存储的数据类型简单的数据类型(POD):兼容C的普通数据类型如int、char、float等复杂的数据类型(非POD)如struct、class(可能存在虚函数)等注意:为了兼顾所以使用模板类要避免伪共享什么是伪共享struct RingBuffer { std::atomicint producer_counter; // 生产者 std::atomicint consumer_counter; // 消费者 // 这两个变量可能在同一个64字节缓存行中 };一个生产者操作A一个消费者操作B二者没有数据竞争但是:CPU的缓存是以缓存行为单位来加载数据的缓存行的大小通常是64字节线程1修改A或者线程2修改B都会造成缓存行标记为脏然后CPU会自动通过缓存一致性协议(MESI),将脏缓存行同步到其他核心MESI频繁同步会导致性能急剧下降解决方案通过内存对齐将A和B放在不同的缓存行当中// 在C当中可以通过alignas来指定对齐方式 struct RingBuffer { alignas(64) std::atomicint producer_counter; // 生产者 alignas(64) std::atomicint consumer_counter; // 消费者 };优化CPU对于位运算的优化比加减乘除要快所以可以使用位运算来代替加减乘除对Capacity的大小优化成2的幂次方取模运算:next (curr 1) % Capacity--------------next (curr 1) (Capacity - 1)接口设计push入队先进行判满再进行入队操作对于简单数据类型(POD),直接使用std::atomic即可对于复杂的数据类型(非POD),可以考虑用到完美转发pop出队先进行判空再进行出队操作size获取队列大小// 入队 templatetypename T bool push(U value) { // 1.先判满 const std::size_t w m_write.load(std::memory_order_relaxed); // SPSC, 考虑到性能最高使用relaxed const std::size_t next_w (w 1) (Capacity - 1); // 哨兵 if (next_w m_read.load(std::memory_order_acquire)) return false; // acquire 保证读到最新的值 // 2.写入数据 new (buffers[w]) T(std::forwardU(value)); m_write.store(next_w, std::memory_order_release); // release 保证写入的值是最新的 return true; } bool pop(T value) { // 1.先判空 const std::size_t r m_read.load(std::memory_order_relaxed); if (r m_write.load(std::memory_order_acquire)) return false; // 2.读取数据 value std::move(*reinterpret_castT*(buffers[r])); // reinterpret_cast 对内存进行重新解释 reinterpret_castT *(buffers[r])-~T(); m_read.store((r 1) (Capacity - 1), std::memory_order_release); return true; } std::size_t size()const { const std::size_t r m_read.load(std::memory_order_acquire); const std::size_t w m_write.load(std::memory_order_acquire); return w r ? (w - r) : (Capacity - r w); }二、无锁编程中的问题2.1、ABA问题什么是ABA问题ABA问题是指线程1读取到变量A线程2将变量A修改为B再修改回A线程1再次读取到变量A但线程1无法判断变量A是否被修改过。ABA问题的危害可能导致数据不一致可能导致逻辑错误在内存回收中尤其危险解决方案使用版本号每次修改变量时增加版本号struct VersionedValue { T value; std::atomicuint64_t version; };使用指针每次修改变量时修改指针指向的地址struct TaggedPointer { void* ptr; uint64_t tag; // 每次修改递增 };使用原子引用每次修改变量时修改引用的值三、有锁队列 VS 无锁队列相对于无锁队列设计就比较简单基本上对于任何操作进行加锁释放锁即可但头疼点也随之而来容易造成死锁。3.1、选择容器有锁队列的选择多种多样可以直接选择STL容器如std::queue、std::deque、std::list等进行动态扩容。3.2、实现// 还是采用模板类方式构造 templatetypename T, typename StorageType std::dequeT class LockedQueue { private: std::mutex m_mutex; StorageType m_storage; public: LockedQueue() default; ~LockedQueue() default; void add(const T item) { lock(); _queue.push_back(item); unlock(); } void lock() { this-_lock.lock(); } void unlock() { this-_lock.unlock(); } void pop_front() { std::lock_guardstd::mutex lock(_lock); _queue.pop_front(); } };自旋锁和互斥锁的区别自旋锁自旋锁是一种用户态忙等待锁当线程A持有自旋锁线程B尝试获取自旋锁时线程B会一直循环等待直到线程A释放自旋锁。无上下文切换自旋锁适用于锁持有时间短的场景如果锁持有时间过长会导致CPU资源浪费。互斥锁互斥锁是一种内核态锁当线程A持有互斥锁线程B尝试获取互斥锁时线程B会被挂起直到线程A释放互斥锁。有上下文切换互斥锁适用于锁持有时间长的场景如果锁持有时间短会导致上下文切换的开销。如何判断锁的持有时间通过锁的持有时间 用户态切内核态内核态切用户态的时间和那么使用互斥锁反之使用自旋锁。四、 总结环形队列是一种逻辑数据结构并非物理数据结构实际上没有环形的内存空间。无锁队列为什么要使用环形队列环形队列可以充分利用内存空间避免内存碎片(malloc/free的开销)对于原子操作友好如果使用STL队列比如queueint q入队可能会涉及到内存分配、节点构造、指针修改等操作这些操作很难进行原子化一般都是加锁。对于常用的STL-vector在push_back时如果空间不足会进行扩容扩容会触发重新分配内存无法保证环形访问而且删除效率低下。无锁队列性能对比类型时间复杂度适用场景性能SPSCO(1)流水线处理最高MPSCO(n)日志收集中等SPMCO(n)事件广播中等MPMCO(n²)通用队列最低无锁队列 VS 有锁队列特性无锁队列有锁队列开销低(无线程上下文切换)高(有线程上下文切换)实现复杂度高低内存开销高低适用场景高并发、低延迟通用

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询