我的编程空间,编程开发者的网络收藏夹
学习永远不晚

怎么使用Go实现健壮的内存型缓存

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

怎么使用Go实现健壮的内存型缓存

本篇内容介绍了“怎么使用Go实现健壮的内存型缓存”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

    使用Go实现健壮的内存型缓存

    本文介绍了缓存的常见使用场景、选型以及注意点,比较有价值。
    译自:Implementing robust in-memory cache with Go

    内存型缓存是一种以消费内存为代价换取应用性能和弹性的方式,同时也推迟了数据的一致性。在使用内存型缓存时需要注意并行更新、错误缓存、故障转移、后台更新、过期抖动,以及缓存预热和转换等问题。

    由来

    缓存是提升性能的最便捷的方式,但缓存不是万能的,在某些场景下,由于事务或一致性的限制,你无法重复使用某个任务的结果。缓存失效是计算机科学中最常见的两大难题之一。

    如果将操作限制在不变的数据上,则无需担心缓存失效。此时缓存仅用于减少网络开销。然而,如果需要与可变数据进行同步,则必须关注缓存失效的问题。

    最简单的方式是基于TTL来设置缓存失效。虽然这种方式看起来逊于基于事件的缓存失效方式,但它简单且可移植性高。由于无法保证事件能够即时传递,因此在最坏的场景中(如事件代理短时间下线或过载),事件甚至还不如TTL精确。

    短TTL通常是性能和一致性之间的一种折衷方式。它可以作为一道屏障来降低高流量下到数据源的负载。

    Demo应用

    下面看一个简单的demo应用,它接收带请求参数的URL,并根据请求参数返回一个JSON对象。由于数据存储在数据库中,因此整个交互会比较慢。

    下面将使用一个名为plt的工具对应用进行压测,plt包括参数:

    • cardinality - 生成的唯一的URLs的数据,会影响到缓存命中率

    • group - 一次性发送的URL相似的请求个数,模拟对相同键的并发访问。

    go run ./cmd/cplt --cardinality 10000 --group 100 --live-ui --duration 10h --rate-limit 5000 curl --concurrency 200 -X 'GET'   'http://127.0.0.1:8008/hello?name=World&locale=ru-RU'   -H 'accept: application/json'

    上述命令会启动一个client,循环发送10000个不同的URLs,每秒发送5000个请求,最大并发数为200。每个URL会以100个请求为批次将进行发送,用以模仿单个资源的并发,下面展示了实时数据:

    怎么使用Go实现健壮的内存型缓存

    Demo应用通过CACHE环境变量定义了三种操作模式:

    • none:不使用缓存,所有请求都会涉及数据库

    • naive:使用简单的map,TTL为3分钟

    • advanced:使用github.com/bool64/cache 库,实现了很多特性来提升性能和弹性,TTL也是3分钟。

    Demo应用的代码位于:github.com/vearutop/cache-story,可以使用make start-deps run命令启动demo应用。

    在不使用缓存的条件下,最大可以达到500RPS,在并发请求达到130之后DB开始因为 Too many connections而阻塞,这种结果不是最佳的,虽然并不严重,但需要提升性能。

    怎么使用Go实现健壮的内存型缓存

    使用advanced缓存的结果如下,吞吐量提升了60倍,并降低了请求延迟以及DB的压力:

    怎么使用Go实现健壮的内存型缓存

    go run ./cmd/cplt --cardinality 10000 --group 100 --live-ui --duration 10h curl --concurrency 100 -X 'GET'   'http://127.0.0.1:8008/hello?name=World&locale=ru-RU'   -H 'accept: application/json'
    Requests per second: 25064.03Successful requests: 15692019Time spent: 10m26.078sRequest latency percentiles:99%: 28.22ms95%: 13.87ms90%: 9.77ms50%: 2.29ms

    字节 VS 结构体

    哪个更佳?

    取决于使用场景,字节缓存([]byte)的优势如下:

    • 数据不可变,在访问数据时需要进行解码

    • 由于内存碎片较少,使用的内存也较少

    • 对垃圾回收友好,因为没有什么需要遍历的

    • 便于在线路上传输

    • 允许精确地限制内存

    字节缓存的最大劣势是编解码带来的开销,在热点循环中,编解码导致的开销可能会非常大。

    结构体的优势:

    • 在访问数据时无需进行编码/解码

    • 更好地表达能力,可以缓存那些无法被序列化的内容

    结构体缓存的劣势:

    • 由于结构体可以方便地进行修改,因此可能会被无意间修改

    • 结构体的内存相对比较稀疏

    • 如果使用了大量长时间存在的结构体,GC可能会花费一定的时间进行遍历,来确保这些结构体仍在使用中,因此会对GC采集器造成一定的压力

    • 几乎无法限制缓存实例的总内存,动态大小的项与其他所有项一起存储在堆中。

    本文使用了结构体缓存。

    Native 缓存

    使用了互斥锁保护的map。当需要检索一个键的值时,首先查看缓存中是否存在该数据以及有没有过期,如果不存在,则需要从数据源构造该数据并将其放到缓存中,然后返回给调用者。

    整个逻辑比较简单,但某些缺陷可能会导致严重的问题。

    并发更新

    当多个调用者同时miss相同的键时,它们会尝试构建数据,这可能会导致死锁或因为缓存踩踏导致资源耗尽。此外如果调用者尝试构建值,则会造成额外的延迟。

    如果某些构建失败,即使缓存中可能存在有效的值,此时父调用者也会失败。

    怎么使用Go实现健壮的内存型缓存

    可以使用低cardinality和高group来模拟上述问题:

    go run ./cmd/cplt --cardinality 100 --group 1000 --live-ui --duration 10h --rate-limit 5000 curl --concurrency 150 -X 'GET'   'http://127.0.0.1:8008/hello?name=World&locale=ru-RU'   -H 'accept: application/json'

    怎么使用Go实现健壮的内存型缓存

    上图展示了使用naive缓存的应用,蓝色标志标识重启并使用advanced缓存。可以看到锁严重影响了性能(Incoming Request Latency)和资源使用(DB Operation Rate)。

    一种解决方案是阻塞并行构建,这样每次只能进行一个构建。但如果有大量并发调用者请求各种键,则可能会导致严重的锁竞争。

    更好的方式是对每个键的构建单独加锁,这样某个调用者就可以获取锁并执行构建,其他调用者则等待构建好的值即可。

    怎么使用Go实现健壮的内存型缓存

    后台更新

    当缓存过期时,需要一个新的值,构建新值可能会比较慢。如果同步进行,则可以减慢尾部延迟(99%以上)。可以提前构建那些被高度需要的缓存项(甚至在数据过期前)。如果可以容忍老数据,也可以继续使用这些数据。

    这种场景下,可以使用老的/即将过期的数据提供服务,并在后台进行更新。需要注意的是,如果构建依赖父上下文,则在使用完老数据之后可能会取消上下文(如满足父HTTP请求),如果我们使用这类上下文来访问数据,则会得到一个context canceled错误。

    解决方案是将上下文与父上下文进行分离,并忽略父上下文的取消行为。

    另外一种策略是主动构建那些即将过期的缓存项,而无需父请求,但这样可能会因为一直淘汰那些无人关心的缓存项而导致资源浪费。

    同步过期

    假设启动了一个使用TTL缓存的实例,由于此时缓存是空的,所有请求都会导致缓存miss并创建值。这样会导致数据源负载突增,每个保存的缓存项的过期时间都非常接近。一旦超过TTL,大部分缓存项几乎会同步过期,这样会导致一个新的负载突增,更新后的值也会有一个非常接近的过期时间,以此往复。

    这种问题常见于热点缓存项,最终这些缓存项会同步更新,但需要花费一段时间。

    对这种问题的解决办法是在过期时间上加抖动。

    如果过期抖动为10%,意味着,过期时间为0.95 * TTL1.05 * TTL。虽然这种抖动幅度比较小,但也可以帮助降低同步过期带来的问题。

    下面例子中,使用高cardinality 和高concurrency模拟这种情况。它会在短时间内请求大量表项,以此构造过期峰值。

    go run ./cmd/cplt --cardinality 10000 --group 1 --live-ui --duration 10h --rate-limit 5000 curl --concurrency 200 -X 'GET' 'http://127.0.0.1:8008/hello?name=World&locale=ru-RU' -H 'accept: application/json'

    怎么使用Go实现健壮的内存型缓存

    从上图可以看出,使用naive缓存无法避免同步过期问题,蓝色标识符表示重启服务并使用带10%抖动的advanced缓存,可以看到降低了峰值,且整体服务更加稳定。

    缓存错误

    当构建值失败,最简单的方式就是将错误返回给调用者即可,但这种方式可能会导致严重的问题。

    例如,当服务正常工作时可以借助缓存处理10K的RPS,但突然出现缓存构建失败(可能由于短时间内数据库过载、网络问题或如错误校验等逻辑错误),此时所有的10K RPS都会命中数据源(因为此时没有缓存)。

    对于高负载系统,使用较短的TTL来缓存错误至关重要。

    故障转移模式

    有时使用过期的数据要好于直接返回错误,特别是当这些数据刚刚过期,这类数据有很大概率等于后续更新的数据。

    故障转移以精确性来换取弹性,通常是分布式系统中的一种折衷方式。

    缓存传输

    缓存有相关的数据时效果最好。

    当启动一个新的实例时,缓存是空的。由于产生有用的数据需要花费一定的时间,因此这段时间内,缓存效率会大大降低。

    有一些方式可以解决"冷"缓存带来的问题。如可以通过遍历数据来预热那些可能有用的数据。

    例如可以从数据库表中拉取最近使用的内容,并将其保存到缓存中。这种方式比较复杂,且并不一定能够生效。

    此外还可以通过定制代码来决定使用哪些数据并在缓存中重构这些表项。但这样可能会对数据库造成一定的压力。

    还可以通过共享缓存实例(如redis或memcached)来规避这种问题,但这也带来了另一种问题,通过网络读取数据要远慢于从本地缓存读取数据。此外,网络带宽也可能成为性能瓶颈,网络数据的编解码也增加了延迟和资源损耗。

    最简单的办法是将缓存从活动的实例传输到新启动的实例中。

    活动实例缓存的数据具有高度相关性,因为这些数据是响应真实用户请求时产生的。

    传输缓存并不需要重构数据,因此不会滥用数据源。

    在生产系统中,通常会并行多个应用实例。在部署过程中,这些实例会被顺序重启,因此总有一个实例是活动的,且具有高质量的缓存。

    Go有一个内置的二进制系列化格式encoding/gob,它可以帮助以最小的代价来传输数据,缺点是这种方式使用了反射,且需要暴露字段。

    使用缓存传输的另一个注意事项是不同版本的应用可能有不兼容的数据结构,为了解决这种问题,需要为缓存的结构添加指纹,并在不一致时停止传输。

    下面是一个简单的实现:

    // RecursiveTypeHash hashes type of value recursively to ensure structural match.func recursiveTypeHash(t reflect.Type, h hash.Hash74, met map[reflect.Type]bool) {    for {        if t.Kind() != reflect.Ptr {            break        }        t = t.Elem()    }    if met[t] {        return    }    met[t] = true    switch t.Kind() {    case reflect.Struct:        for i := 0; i < t.NumField(); i++ {            f := t.Field(i)            // Skip unexported field.            if f.Name != "" && (f.Name[0:1] == strings.ToLower(f.Name[0:1])) {                continue            }            if !f.Anonymous {                _, _ = h.Write([]byte(f.Name))            }            recursiveTypeHash(f.Type, h, met)        }    case reflect.Slice, reflect.Array:        recursiveTypeHash(t.Elem(), h, met)    case reflect.Map:        recursiveTypeHash(t.Key(), h, met)        recursiveTypeHash(t.Elem(), h, met)    default:        _, _ = h.Write([]byte(t.String()))    }}

    可以通过HTTP或其他合适的协议来传输缓存数据,本例中使用了HTTP,代码为/debug/transfer-cache。注意,缓存可能会包含不应该对外暴露的敏感信息。

    在本例中,可以借助于单个启用了不同端口的应用程序实例来执行传输:

    CACHE_TRANSFER_URL=http://127.0.0.1:8008/debug/transfer-cache HTTP_LISTEN_ADDR=127.0.0.1:8009 go run main.go
    2022-05-09T02:33:42.871+0200    INFO    cache/http.go:282       cache restored  {"processed": 10000, "elapsed": "12.963942ms", "speed": "39.564084 MB/s", "bytes": 537846}2022-05-09T02:33:42.874+0200    INFO    brick/http.go:66        starting server, Swagger UI at http://127.0.0.1:8009/docs2022-05-09T02:34:01.162+0200    INFO    cache/http.go:175       cache dump finished     {"processed": 10000, "elapsed": "12.654621ms", "bytes": 537846, "speed": "40.530944 MB/s", "name": "greetings", "trace.id": "31aeeb8e9e622b3cd3e1aa29fa3334af", "transaction.id": "a0e8d90542325ab4"}

    怎么使用Go实现健壮的内存型缓存

    上图中蓝色标识标识应用重启,最后两条为缓存传输。可以看到性能不受影响,而在没有缓存传输的情况下,会受到严重的预热惩罚。

    一个不那么明显的好处是,可以将缓存数据传输到本地开发机器,用于重现和调试生产环境的问题。

    锁竞争和底层性能

    基本每种缓存实现都会使用键值映射来支持并发访问(通常是读)。

    大多数场景下可以忽略底层性能带来的影响。例如,如果使用内存型缓存来处理HTTP API,使用最简单的map+mutex就足够了,这是因为IO操作所需的时间要远大于内存操作。记住这一点很重要,以免过早地进行优化以及增加不合理的复杂性。

    如果依赖内存型缓存的应用是CPU密集型的,此时锁竞争可能会影响到整体性能。

    为了避免并发读写下的数据冲突,可能会引入锁竞争。在使用单个互斥锁的情况下,这种同步可能会限制同一时间内只能进行一个操作,这也意味着多核CPU可能无法发挥作用。

    对于以读为主的负载,标准的sync.Map 就可以满足性能要求,但对于以写为主的负载,则会降低其性能。有一种比sync.Map性能更高的方式github.com/puzpuzpuz/xsync.Map,它使用了 Cache-Line Hash Table (CLHT)数据结构。

    另一种常见的方式是通过map分片的方式(fastcache, bigcache, bool64/cache)来降低锁竞争,这种方式基于键将值分散到不同的桶中,在易用性和性能之间做了折衷。

    内存管理

    内存是一个有限的资源,因此缓存不能无限增长。

    过期的元素需要从缓存中淘汰,这个步骤可以同步执行,也可以在后台执行。使用后台回收方式不会阻塞应用本身,且如果将后台回收进程配置为延迟回收的方式时,在需要故障转移时就可以使用过期的数据。

    如果上述淘汰过期数据的方式无法满足内存回收的要求,可以考虑使用其他淘汰策略。在选择淘汰策略时需要平衡CPU/内存使用和命中/丢失率。总之,淘汰的目的是为了在可接受的性能预算内优化命中/丢失率,这也是评估一个淘汰策略时需要注意的指标。

    下面是常见的选择淘汰策略的原则:

    • 最近最少频率使用(LFU),需要在每次访问时维护计数器

    • 最近最少使用(LRU),需要在每次访问时更新元素的时间戳或顺序

    • 先进先出(FIFO),一旦创建缓存就可以使用缓存中的数据,比较轻量

    • 随机元素,性能最佳,不需要任何排序,但精确性最低

    上述给出了如何选项一个淘汰策略,下一个问题是"何时以及应该淘汰多少元素?"。

    对于[]byte缓存来说,该问题比较容易解决,因为大多数实现中都精确提供了控制内存的方式。

    但对于结构体缓存来说就比较棘手了。在应用执行过程中,很难可靠地确定特定结构体对堆内存的影响,GC可能会获取到这些内存信息,但应用本身则无法获取。下面两种获取结构体内存的指标精确度不高,但可用:

    • 缓存中的元素个数

    • 应用使用的总内存

    由于这些指标并不与使用的缓存内存成线性比例,因此不能据此计算需要淘汰的元素。一种比较合适的方式是在触发淘汰时,淘汰一部分元素(如占使用内存10%的元素)。

    缓存数据的堆影响很大程度上与映射实现有关。可以从下面的性能测试中看到,相比于二进制序列化(未压缩)的数据,map[string]struct{...}占用的内存是前者的4倍。

    基准测试

    下面是保存1M小结构体(struct { int, bool, string })的基准测试,验证包括10%的读操作以及0.1%的写操作。字节缓存通过编解码结构体来验证。

    goos: darwingoarch: amd64cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
    name             MB/inuse   time/op (10%) time/op (0.1%)      sync.Map         192 ± 0%   142ns ± 4%    29.8ns ±10%   // Great for read-heavy workloads.shardedMap       196 ± 0%   53.3ns ± 3%   28.4ns ±11%   mutexMap         182 ± 0%   226ns ± 3%    207ns ± 1%    rwMutexMap       182 ± 0%   233ns ± 2%    67.8ns ± 2%   // RWMutex perf degrades with more writes.shardedMapOf     181 ± 0%   50.3ns ± 3%   27.3ns ±13%   ristretto        346 ± 0%   167ns ± 8%    54.1ns ± 4%   // Failed to keep full working set, ~7-15% of the items are evicted.xsync.Map        380 ± 0%   31.4ns ± 9%   22.0ns ±14%   // Fastest, but a bit hungry for memory.patrickmn        184 ± 0%   373ns ± 1%    72.6ns ± 5%   bigcache         340 ± 0%   75.8ns ± 8%   72.9ns ± 3%   // Byte cache.freecache        333 ± 0%   98.1ns ± 0%   77.8ns ± 2%   // Byte cache.fastcache       44.9 ± 0%   60.6ns ± 8%   64.1ns ± 5%   // A true champion for memory usage, while having decent performance.

    如果实际场景支持序列化,那么fastcache可以提供最佳的内存使用(fastcache使用动态申请的方式来分配内存)

    对于CPU密集型的应用,可以使用xsync.Map。

    从上述测试可以看出,字节缓存并不一定意味着高效地利用内存,如bigcachefreecache

    开发者友好

    程序并不会总是按照我们期望的方式允许,复杂的逻辑会导致很多非预期的问题,也很难去定位。不幸的是,缓存使得程序的状况变得更糟,这也是为什么让缓存更友好变得如此重要。

    缓存可能成为多种问题的诱发因素,因此应该尽快安全地清理相关缓存。为此,可以考虑对所有缓存的元素进行校验,在高载情况下,失效不一定意味着“删除”,一旦一次性删除所有缓存,数据源可能会因为过载而失败。更优雅的方式是为所有元素设置过期时间,并在后台进行更新,更新过程中使用老数据提供服务。

    如果有人正在调查特定的数据源问题,缓存项可能会因为过期而误导用户。可以禁用特定请求的缓存,这样就可以排除缓存带来的不精确性。可以通过特定的请求头以及并在中间件上下文中实现。注意这类控制并不适用于外部用户(会导致DOS攻击)。

    “怎么使用Go实现健壮的内存型缓存”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

    免责声明:

    ① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

    ② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

    怎么使用Go实现健壮的内存型缓存

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

    下载Word文档

    猜你喜欢

    怎么使用Go实现健壮的内存型缓存

    本篇内容介绍了“怎么使用Go实现健壮的内存型缓存”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!使用Go实现健壮的内存型缓存本文介绍了缓存的常
    2023-06-30

    Linux内存管理中的slab缓存怎么实现

    本文小编为大家详细介绍“Linux内存管理中的slab缓存怎么实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“Linux内存管理中的slab缓存怎么实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。Linux
    2023-06-16

    怎么使用Spring提供的不同缓存注解实现缓存

    这篇文章主要介绍了怎么使用Spring提供的不同缓存注解实现缓存的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇怎么使用Spring提供的不同缓存注解实现缓存文章都会有所收获,下面我们一起来看看吧。前言缓存可以通
    2023-07-06

    SpringBoot怎么使用Caffeine实现缓存

    这篇文章主要介绍“SpringBoot怎么使用Caffeine实现缓存”,在日常操作中,相信很多人在SpringBoot怎么使用Caffeine实现缓存问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Sprin
    2023-07-02

    odoo中怎么使用redis实现缓存

    本篇内容主要讲解“odoo中怎么使用redis实现缓存”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“odoo中怎么使用redis实现缓存”吧!Odoo中使用Redis实现缓存可以提高系统性能,避
    2023-07-05

    怎么在HTML5中使用manifest实现离线缓存

    本篇文章给大家分享的是有关怎么在HTML5中使用manifest实现离线缓存,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。离线访问对基于网络的应用而言越来越重要。虽然所有浏览器
    2023-06-09

    C#中怎么使用Couchbase实现分布式缓存

    C#中怎么使用Couchbase实现分布式缓存,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。一、简介 目前C#业界使用得最多的 Cache 系统主要是 Memcached和
    2023-06-17

    Python中怎么使用装饰器实现函数的缓存

    这篇文章主要介绍“Python中怎么使用装饰器实现函数的缓存”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Python中怎么使用装饰器实现函数的缓存”文章能帮助大家解决问题。装饰器模式在以下场景中被
    2023-07-05

    怎么使用SignalR和Redis实现实时分布式缓存

    要实现实时分布式缓存,可以结合使用SignalR和Redis。首先,通过SignalR建立一个实时通信的连接,可以让不同的服务之间实时地传递消息和数据。然后,借助Redis作为分布式缓存存储,可以在不同的服务之间共享缓存数据。具体步骤
    怎么使用SignalR和Redis实现实时分布式缓存
    2024-05-09

    使用SpringBoot怎么对Redis进行集成来实现缓存

    本篇文章给大家分享的是有关使用SpringBoot怎么对Redis进行集成来实现缓存,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Redis简介Redis 是一个开源(BSD许
    2023-05-31

    spring boot 中的hibernate怎么使用ehcache 2.x实现二级缓存

    今天就跟大家聊聊有关spring boot 中的hibernate怎么使用ehcache 2.x实现二级缓存,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。项目依赖
    2023-05-31

    Linux中使用全局框架怎么实现内存管理

    Linux中使用全局框架怎么实现内存管理,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。一. 地址划分。1. CPU地址。CPU地址是指CPU的地址总线能寻址的
    2023-06-16

    怎么在Android应用中实现一个查看内存使用情况功能

    怎么在Android应用中实现一个查看内存使用情况功能?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。创建项目Android清单文件
    2023-05-31

    编程热搜

    • Python 学习之路 - Python
      一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
      Python 学习之路 - Python
    • chatgpt的中文全称是什么
      chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
      chatgpt的中文全称是什么
    • C/C++中extern函数使用详解
    • C/C++可变参数的使用
      可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
      C/C++可变参数的使用
    • css样式文件该放在哪里
    • php中数组下标必须是连续的吗
    • Python 3 教程
      Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
      Python 3 教程
    • Python pip包管理
      一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
      Python pip包管理
    • ubuntu如何重新编译内核
    • 改善Java代码之慎用java动态编译

    目录