iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++高并发场景下读多写少的优化方案是什么
  • 764
分享到

C++高并发场景下读多写少的优化方案是什么

2023-06-26 04:06:28 764人浏览 安东尼
摘要

这期内容当中小编将会给大家带来有关c++高并发场景下读多写少的优化方案是什么,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。概述一谈到高并发的优化方案,往往能想到模块水平拆分、数据库读写分离、分库分表,加缓

这期内容当中小编将会给大家带来有关c++高并发场景下读多写少的优化方案是什么,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。


概述

一谈到高并发的优化方案,往往能想到模块水平拆分、数据库读写分离、分库分表,加缓存、加MQ等,这些都是从系统架构上解决。单模块作为系统的组成单元,其性能好坏也能很大的影响整体性能,本文从单模块下读多写少的场景出发,探讨其解决方案,以其更好的实现高并发。
不同的业务场景,读和写的频率各有侧重,有两种常见的业务场景:

  • 读多写少:典型场景如广告检索端、白名单更新维护、loadbalancer

  • 读少写多:典型场景如qps统计

本文针对读多写少(也称一写多读)场景下遇到的问题进行分析,并探讨一种合适的解决方案。

分析

读多写少的场景,服务大部分情况下都是处于读,而且要求读的耗时不能太长,一般是毫秒或者更低的级别;更新的频率就不是那么频繁,如几秒钟更新一次。通过简单的加互斥,腾出一片临界区,往往能到达预期的效果,保证数据更新正确。

C++高并发场景下读多写少的优化方案是什么

但是,只要加了锁,就会带来竞争,即使加的是读写锁,虽然读之间不互斥,但写一样会影响读,而且读写同时争夺锁的时候,锁优先分配给写(读写锁的特性)。例如,写的时候,要求所有的读请求阻塞住,等到写线程或协程释放锁之后才能读。如果写的临界区耗时比较大,则所有的读请求都会受影响,从监控图上看,这时候会有一根很尖的耗时毛刺,所有的读请求都在队列中等待处理,如果在下个更新周期来之前,服务能处理完这些读请求,可能情况没那么糟糕。但极端情况下,如果下个更新周期来了,读请求还没处理完,就会形成一个恶性循环,不断的有读请求在队列中等待,最终导致队列被挤满,服务出现假死,情况再恶劣一点的话,上游服务发现某个节点假死后,由于负载均衡策略,一般会重试请求其他节点,这时候其他节点的压力跟着增加了,最终导致整个系统出现雪崩。

因此,加锁在高并发场景下要尽量避免,如果避免不了,需要让锁的粒度尽量小,接近无锁(lock-free)更好,简单的对一大片临界区加锁,在高并发场景下不是一种合适的解决方案

双缓冲

有一种数据结构叫双缓冲,其这种数据结构很常见,例如显示屏的显示原理,显示屏显示的当前帧,下一帧已经在后台的buffer准备好,等时间周期一到,就直接替换前台帧,这样能做到无卡顿的刷新,其实现的指导思想是空间换时间,这种数据结构的工作原理如下:

  • 数据分为前台和后台

  • 所有读线程读前台数据,不用加锁,通过一个指针来指向当前读的前台数据

  • 只有一个线程负责更新,更新的时候,先准备好后台数据,接着直接切指针,这之后所有新进来的读请求都看到了新的前台数据

  • 有部分读还落在老的前台那里处理,因为更新还不算完成,也就不能退出写线程,写线程需要等待所有落在老前台的线程读完成后,才能退出,在退出之前,顺便再更新一遍老前台数据(也就当前的新后台),可以保证前后台数据一致,这点在做增量更新的时候有用

C++高并发场景下读多写少的优化方案是什么

工程实现上需要攻克的难点

写线程要怎么知道所有的读线程在老前台中的读完成了呢?

一种做法是让各个读线程都维护一把锁,读的时候锁住,这时候不会影响其他线程的读,但会影响写,读完后释放锁(某些时候可能会有通知写线程的开销,但写本身很少),写线程只需要确认锁有没有释放了,确认完了后马上释放,确认这个动作非常快(小于25ns,1s=103ms=106us=10^9ns),读线程几乎不会感觉到锁的存在。

每个线程都有一把自己的锁,需要用全局的map来做线程id和锁的映射吗?

不需要,而且这样做全局map就要加全局锁了,又回到了刚开始分析中遇到的问题了。其实,每个线程可以有私有存储(thread local storage,简称TLS),如果是协程,就对应这协程的TLS(但对于Go语言,官方是不支持TLS的,想实现类似功能,要么就想办法获取到TLS,要么就不要基于协程锁,而是用全局锁,但尽量让锁粒度小,本文主要针对C++语言,暂时不深入讨论其他语言的实现)。这样每个读线程锁的是自己的锁,不会影响到其他的读线程,锁的目的仅仅是为了保证读优先。
对于线程私有存储,可以使用pthread_key_create, pthread_setspecific,pthread_getspecific系列函数

核心代码实现

template <typename T, typename TLS>int DoublyBufferedData<T, TLS>::Read(    typename DoublyBufferedData<T, TLS>::ScopedPtr* ptr) { // ScopedPtr析构的时候,会释放锁    Wrapper* w = static_cast<Wrapper*>(pthread_getspecific(_wrapper_key)); //非首次读,获取pthread local lock    if (BaiDU_LIKELY(w != NULL)) {        w->BeginRead();    // 锁住        ptr->_data = UnsafeRead();        ptr->_w = w;        return 0;    }    w = AddWrapper();    if (BAIDU_LIKELY(w != NULL)) {        const int rc = pthread_setspecific(_wrapper_key, w); // 首次读,设置pthread local lock        if (rc == 0) {            w->BeginRead();            ptr->_data = UnsafeRead();            ptr->_w = w;            return 0;        }    }    return -1;}

template <typename T, typename TLS>template <typename Fn>size_t DoublyBufferedData<T, TLS>::Modify(Fn& fn) {    BAIDU_SCOPED_LOCK(_modify_mutex); // 加锁,保证只有一个写    int bg_index = !_index.load(butil::memory_order_relaxed); // 指向后台buffer    const size_t ret = fn(_data[bg_index]); // 修改后台buffer    if (!ret) {        return 0;    }    // 切指针    _index.store(bg_index, butil::memory_order_release);        bg_index = !bg_index;    // 等所有读老前台的线程读结束    {        BAIDU_SCOPED_LOCK(_wrappers_mutex);        for (size_t i = 0; i < _wrappers.size(); ++i) {            _wrappers[i]->WaitReadDone();        }    }    // 确认没有读了,直接修改新后台数据,对其新前台    const size_t ret2 = fn(_data[bg_index]);    return ret2;}

完整实现请参考brpc的DoublyBufferData

简单说说golang中双缓冲的实现

普通的双缓冲加载实现

基于计数器,用atomic,保证原子性,读进入临界区,计数器+1,退出-1,写判断计数器为0则切换,但计数器是全局锁。这种方案C++也可以采取,只是计数器毕竟也是全局锁,性能会差那么一丢丢。即使用智能指针shared_ptr,也会面临智能指针引用计数互斥的问题。之所以用计数器,而不用TLS,是因为go不支持TLS,对比TLS版本和计数器版本,TLS性能更优,因为没有抢计数器的互斥问题,但抢计数器本身很快,性能没测试过,可以试试。

sync.Map的实现

也是基于计数器,只是计数器是为了让读前台缓存失效的概率不要太高,有抑制和收敛的作用,实现了读的无锁,少部分情况下,前台缓存读不到数据的时候,会去读后台缓存,这时候也要加锁,同时计数器+1。计数器数值达到一定程度(超过后台缓存的元素个数),就执行切换

是否适用于读少写多的场景

不合适,双缓冲优先保证读的性能,写多读少的场景需要优先保证写的性能。

上述就是小编为大家分享的C++高并发场景下读多写少的优化方案是什么了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注编程网其他教程频道。

--结束END--

本文标题: C++高并发场景下读多写少的优化方案是什么

本文链接: https://www.lsjlt.com/news/306700.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • C++高并发场景下读多写少的优化方案是什么
    这期内容当中小编将会给大家带来有关C++高并发场景下读多写少的优化方案是什么,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。概述一谈到高并发的优化方案,往往能想到模块水平拆分、数据库读写分离、分库分表,加缓...
    99+
    2023-06-26
  • 浅谈C++高并发场景下读多写少的优化方案
    目录概述分析双缓冲工程实现上需要攻克的难点核心代码实现简单说说golang中双缓冲的实现相关文献来源:https://www.cnblogs.com/longbozhan/p/157...
    99+
    2024-04-02
  • PHP并发场景的解决方案是什么
    本篇文章给大家分享的是有关PHP并发场景的解决方案是什么,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。在秒杀,抢购等并发场景下,可能会出现超卖...
    99+
    2024-04-02
  • Redis:高并发场景下的数据存储解决方案
    Redis:高并发场景下的数据存储解决方案随着互联网的迅速发展,高并发场景下的数据存储已成为各大企业关注的焦点。在面对海量请求和快速响应的需求时,传统的关系型数据库面临性能瓶颈。而Redis作为一种高性能的非关系型数据库,逐渐成为高并发场景...
    99+
    2023-11-07
    解决方案 数据存储 高并发
  • 使用Golang的同步机制优化高并发场景下的性能
    在高并发场景下,使用Golang的同步机制可以优化性能。以下是一些优化建议:1. 使用互斥锁(Mutex):在访问共享资源时使用互斥...
    99+
    2023-10-08
    Golang
  • mysql高并发优化的方法是什么
    数据库设计优化:合理设计表结构,减少不必要的字段和索引,避免使用大字段类型,适当拆分大表等。 索引优化:合理创建索引,尽量避...
    99+
    2024-04-03
    mysql
  • tomcat高并发优化的方法是什么
    Tomcat高并发优化的方法包括:1. 配置线程池:通过调整Tomcat的线程池配置参数,可以提高并发处理能力。可以增加线程池的最大...
    99+
    2023-10-12
    tomcat
  • Flink+HBase场景化的解决方案是什么
    Flink+HBase场景化的解决方案是什么,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。Flink+HBase所提供实时计算场景解决方案。实时计算市场竞争分析...
    99+
    2023-06-19
  • MySQL在大数据、高并发场景下的SQL语句优化和实践是怎样的
    MySQL在大数据、高并发场景下的SQL语句优化和实践是怎样的,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你...
    99+
    2024-04-02
  • 理解MySQL MVCC 原理,优化多用户并发场景下的查询性能
    随着互联网的迅猛发展,数据库成为了大多数企业的核心基础设施之一。在数据库中,查询性能是一个重要的指标,尤其是在多用户并发场景下。一个高效的数据库应该能够处理大量的查询请求,并同时保持较低的响应时间。为了实现这一目标,MySQL引入了MVCC...
    99+
    2023-10-22
    优化 并发 MySQL MVCC
  • Go语言如何通过优化数据结构提高高并发场景下的效率?
    在高并发场景中优化go语言数据结构以提升效率:使用并发安全的数据结构:sync.mutex、sync.map、sync.waitgroup选择合适的数据结构:频繁读写的线性结构(map、...
    99+
    2024-05-10
    git golang go语言 键值对
  • Golang高并发场景中的锁管理策略是什么?
    锁管理策略提升 golang 高并发性能锁类型: 互斥锁(mutex),读写锁(rwmutex),原子操作最佳实践:最小化锁的粒度避免死锁使用读写锁使用原子操作实战案例: 使用互斥锁保护...
    99+
    2024-05-10
    golang 并发 go语言
  • 高并发环境下Redis序列化的方法是什么
    在高并发环境下,Redis序列化的方法可以选择使用二进制序列化或者JSON序列化。 二进制序列化:使用二进制序列化可以减少序列化...
    99+
    2024-04-29
    Redis
  • Java架构高并发的解决方案是什么
    Java架构高并发的解决方案是什么,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。1.应用和静态资源分离刚开始的时候应用和静态资源是保存在一起的,当并发量达到一定程度的时候就需要...
    99+
    2023-06-16
  • mysql多进程并发读取数据的方法是什么
    MySQL是一个单进程应用程序,但可以通过多线程来实现并发读取数据。以下是一些常见的方法:1. 使用连接池:建立一个连接池,多个线程...
    99+
    2023-08-23
    mysql
  • c语言多进程并发的方法是什么
    C语言中多进程并发的方法有以下几种:1. fork()函数:通过调用fork()函数创建一个新的进程,使得原有的进程(父进程)和新创...
    99+
    2023-08-23
    c语言
  • Java高并发下的流量控制方法是什么
    今天小编给大家分享一下Java高并发下的流量控制方法是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。这个时候如果不做任何...
    99+
    2023-06-16
  • C#提高StringBuilder操作性能优化的方法是什么
    本篇内容介绍了“C#提高StringBuilder操作性能优化的方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!在 .NET 中,S...
    99+
    2023-06-25
  • Go 并发编程算法:Linux 环境下的挑战与解决方案是什么?
    随着计算机技术的不断发展,多核处理器已经成为了现代计算机的标配。然而,在多核处理器上进行并发编程并不容易,需要开发人员充分利用多核处理器的计算资源来提高程序的性能。Go 语言是一种面向并发编程的语言,它提供了一系列的并发编程工具和算法,可...
    99+
    2023-08-08
    并发 编程算法 linux
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作