执著的守望 發表於 2022-10-18 13:36:00

Go 每日一库之 go-cache 缓存

<p>什么是&nbsp;go-cache</p>
<p>&nbsp;</p>
<p data-tool="mdnice编辑器">go-cache 是一个轻量级的基于内存的 K-V 储存组件,内部实现了一个线程安全的 mapinterface{},适用于单机应用。具备如下功能:</p>
<ul class="list-paddingleft-1" data-tool="mdnice编辑器">
<li>线程安全,多 goroutine 并发安全访问;</li>
<li>每个 item 可以设置过期时间(或无过期时间);</li>
<li>自动定期清理过期的 item;</li>
<li>可以自定义清理回调函数;</li>
</ul>
<p data-tool="mdnice编辑器">这里的 item 指的是 map 里的元素。</p>
<p data-tool="mdnice编辑器">go-cache 一般用作临时数据缓存来使用,而不是持久性的数据存储。对于某些停机后快速恢复的场景,go-cache支持将缓存数据保存到文件,恢复时从文件中将数据加载到内存。</p>
<p data-tool="mdnice编辑器">先来“浅尝”下,好不好用试试就知道。</p>
<h3 data-tool="mdnice编辑器">如何使用</h3>
<pre data-tool="mdnice编辑器"><code>func&nbsp;main()&nbsp;{<br>&nbsp;c&nbsp;:=&nbsp;cache.New(10*time.Second,&nbsp;30*time.Second)&nbsp;//&nbsp;默认过期时间10s;清理间隔30s,即每30s钟会自动清理过期的键值对<br><br>&nbsp;//&nbsp;设置一个键值对,过期时间是&nbsp;3s<br>&nbsp;c.Set("a",&nbsp;"testa",&nbsp;3*time.Second)<br><br>&nbsp;//&nbsp;设置一个键值对,采用&nbsp;New()&nbsp;时的默认过期时间,即&nbsp;10s<br>&nbsp;c.Set("foo",&nbsp;"bar",&nbsp;cache.DefaultExpiration)<br><br>&nbsp;//&nbsp;设置一个键值对,没有过期时间,不会自动过期,需要手动调用&nbsp;Delete()&nbsp;才能删除<br>&nbsp;c.Set("baz",&nbsp;42,&nbsp;cache.NoExpiration)<br><br>&nbsp;v,&nbsp;found&nbsp;:=&nbsp;c.Get("a")<br>&nbsp;fmt.Println(v,&nbsp;found)&nbsp;//&nbsp;testa,true<br><br>&nbsp;&lt;-time.After(5&nbsp;*&nbsp;time.Second)&nbsp;//&nbsp;延时5s<br><br>&nbsp;v,&nbsp;found&nbsp;=&nbsp;c.Get("a")&nbsp;//&nbsp;nil,false<br>&nbsp;fmt.Println(v,&nbsp;found)<br><br>&nbsp;&lt;-time.After(6&nbsp;*&nbsp;time.Second)<br>&nbsp;v,&nbsp;found&nbsp;=&nbsp;c.Get("foo")&nbsp;//&nbsp;nil,false<br>&nbsp;fmt.Println(v,&nbsp;found)<br><br>&nbsp;v,&nbsp;found&nbsp;=&nbsp;c.Get("baz")&nbsp;//&nbsp;42,true<br>&nbsp;fmt.Println(v,&nbsp;found)<br><br>&nbsp;//&nbsp;完整例子请关注公众号【Golang来啦】,后台发送关键字&nbsp;gocache&nbsp;获取<br>&nbsp;//TestCache()<br>&nbsp;//TestCacheTimes()<br>&nbsp;//TestNewFrom()<br>&nbsp;//TestOnEvicted()<br>&nbsp;//TestFileSerialization()<br>}<br><br></code></pre>
<p data-tool="mdnice编辑器">下面我们来看下 go-cache 内部是如何实现前面说的那些功能的,另外阅读完源码我们还能掌握一种优雅地关闭后台 goroutine 的方法。</p>
<h3 data-tool="mdnice编辑器">常量与结构体</h3>
<h4 data-tool="mdnice编辑器">常量</h4>
<pre data-tool="mdnice编辑器"><code>const&nbsp;(<br>&nbsp;<br>&nbsp;NoExpiration&nbsp;time.Duration&nbsp;=&nbsp;-1&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;无有效时间<br><br>&nbsp;DefaultExpiration&nbsp;time.Duration&nbsp;=&nbsp;0&nbsp;&nbsp;&nbsp;//&nbsp;表示采用默认时间<br>)<br></code></pre>
<p data-tool="mdnice编辑器">这两个参数可以用作 New() 函数的第一个入参,则默认过期时间小于0,意味着添加键值对时如果采用默认过期时间,则该键值对不会过期,因为 DeleteExpired() 方法会判断 v.Expiration 是否大于 0,大于 0 时才会自动删除。如果想删除需要手动 Delete() 方法。</p>
<p data-tool="mdnice编辑器">添加键值对,比如执行 Set()、Add() 等操作时,这两个常量也可以作为参数,NoExpiration 表示没有过期时间,DefaultExpiration 表示采用默认的过期时间。</p>
<h4 data-tool="mdnice编辑器">结构体</h4>
<p data-tool="mdnice编辑器">主要的结构体包括下面这些:</p>
<pre data-tool="mdnice编辑器"><code><br>type&nbsp;Item&nbsp;struct&nbsp;{&nbsp;&nbsp;//&nbsp;键值对<br>&nbsp;Object&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;interface{}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;存放&nbsp;K-V&nbsp;的值,可以存放任何类型的值<br>&nbsp;Expiration&nbsp;int64&nbsp;&nbsp;&nbsp;//&nbsp;键值对的过期时间(绝对时间)<br>}<br><br>type&nbsp;Cache&nbsp;struct&nbsp;{&nbsp;&nbsp;&nbsp;//&nbsp;对外使用的&nbsp;Cache<br>&nbsp;*cache&nbsp;&nbsp;//&nbsp;cache&nbsp;实例<br>}<br><br>type&nbsp;cache&nbsp;struct&nbsp;{<br>&nbsp;defaultExpiration&nbsp;time.Duration&nbsp;&nbsp;&nbsp;//&nbsp;默认的过期时间,添加一个键值对时如果设置默认的过期时间(即代码里的&nbsp;DefaultExpiration)则会使用到该值<br>&nbsp;items&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mapItem&nbsp;&nbsp;&nbsp;//&nbsp;存放的键值对<br>&nbsp;mu&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sync.RWMutex&nbsp;&nbsp;&nbsp;//&nbsp;读写锁<br>&nbsp;onEvicted&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;func(string,&nbsp;interface{})&nbsp;&nbsp;//&nbsp;删除key时的回调函数<br>&nbsp;janitor&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*janitor&nbsp;&nbsp;//&nbsp;定期清理器&nbsp;定期检查过期的&nbsp;Item<br>}<br><br>type&nbsp;janitor&nbsp;struct&nbsp;{&nbsp;//&nbsp;清理器结构体<br>&nbsp;Interval&nbsp;time.Duration&nbsp;//&nbsp;清理时间间隔<br>&nbsp;stop&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;chan&nbsp;bool&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;是否停止<br>}<br><br><br></code></pre>
<h3 data-tool="mdnice编辑器">主要流程和功能</h3>
<h4 data-tool="mdnice编辑器">实例化 Cache</h4>
<pre data-tool="mdnice编辑器"><code><br>//&nbsp;参数:默认过期时间、清理时间间隔<br>func&nbsp;New(defaultExpiration,&nbsp;cleanupInterval&nbsp;time.Duration)&nbsp;*Cache&nbsp;{<br>&nbsp;items&nbsp;:=&nbsp;make(mapItem)&nbsp;&nbsp;&nbsp;//&nbsp;初始化&nbsp;items<br>&nbsp;return&nbsp;newCacheWithJanitor(defaultExpiration,&nbsp;cleanupInterval,&nbsp;items)&nbsp;&nbsp;&nbsp;//&nbsp;根据需要是否创建清理器<br>}<br><br>func&nbsp;newCacheWithJanitor(de&nbsp;time.Duration,&nbsp;ci&nbsp;time.Duration,&nbsp;m&nbsp;mapItem)&nbsp;*Cache&nbsp;{<br>&nbsp;c&nbsp;:=&nbsp;newCache(de,&nbsp;m)&nbsp;&nbsp;&nbsp;//&nbsp;实例化cache<br>&nbsp;C&nbsp;:=&nbsp;&amp;Cache{c}<br>&nbsp;if&nbsp;ci&nbsp;&gt;&nbsp;0&nbsp;{&nbsp;//&nbsp;清理间隔大于&nbsp;0&nbsp;时才会开启自动清除过期item的功能,否则需要手动调用删除方法-DeleteExpired()&nbsp;删除过期item<br>&nbsp;&nbsp;runJanitor(c,&nbsp;ci)&nbsp;&nbsp;&nbsp;//&nbsp;开启清理器<br>&nbsp;&nbsp;runtime.SetFinalizer(C,&nbsp;stopJanitor)&nbsp;&nbsp;&nbsp;//&nbsp;优雅地停止&nbsp;goroutine&nbsp;的一种方式,我们放在后面讲<br>&nbsp;}<br>&nbsp;return&nbsp;C<br>}<br><br>//&nbsp;如果&nbsp;de&nbsp;小于等于0,则默认过期时间小于0,意味着添加键值对时如果采用默认过期时间,则该键值对不会过期<br>//&nbsp;因为 DeleteExpired()&nbsp;方法会判断 v.Expiration 是否大于&nbsp;0,大于&nbsp;0&nbsp;时才会自动删除。&nbsp;如果想删除需要手动 Delete()&nbsp;方法。<br>func&nbsp;newCache(de&nbsp;time.Duration,&nbsp;m&nbsp;mapItem)&nbsp;*cache&nbsp;{<br>&nbsp;if&nbsp;de&nbsp;==&nbsp;0&nbsp;{<br>&nbsp;&nbsp;de&nbsp;=&nbsp;-1<br>&nbsp;}<br>&nbsp;c&nbsp;:=&nbsp;&amp;cache{<br>&nbsp;&nbsp;defaultExpiration:&nbsp;de,<br>&nbsp;&nbsp;items:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m,<br>&nbsp;}<br>&nbsp;return&nbsp;c<br>}<br><br>//&nbsp;运行清理器<br>func&nbsp;runJanitor(c&nbsp;*cache,&nbsp;ci&nbsp;time.Duration)&nbsp;{<br>&nbsp;j&nbsp;:=&nbsp;&amp;janitor{&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;实例化清理器<br>&nbsp;&nbsp;Interval:&nbsp;ci,<br>&nbsp;&nbsp;stop:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;make(chan&nbsp;bool),<br>&nbsp;}<br>&nbsp;c.janitor&nbsp;=&nbsp;j<br>&nbsp;go&nbsp;j.Run(c)&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;开协程,定时清理器跑起来!!<br>}<br><br>func&nbsp;(j&nbsp;*janitor)&nbsp;Run(c&nbsp;*cache)&nbsp;{<br>&nbsp;ticker&nbsp;:=&nbsp;time.NewTicker(j.Interval)&nbsp;&nbsp;//&nbsp;开定时器<br>&nbsp;for&nbsp;{<br>&nbsp;&nbsp;select&nbsp;{<br>&nbsp;&nbsp;case&nbsp;&lt;-ticker.C:<br>&nbsp;&nbsp;&nbsp;c.DeleteExpired()&nbsp;&nbsp;&nbsp;//&nbsp;定时调用&nbsp;DeleteExpired()&nbsp;执行过期删除操作<br>&nbsp;&nbsp;case&nbsp;&lt;-j.stop:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;接收到停止清理器的信号,下面便停止定时器并返回,退出协程<br>&nbsp;&nbsp;&nbsp;ticker.Stop()<br>&nbsp;&nbsp;&nbsp;return<br>&nbsp;&nbsp;}<br>&nbsp;}<br>}<br><br></code></pre>
<h3 data-tool="mdnice编辑器">添加操作</h3>
<p data-tool="mdnice编辑器">添加类操作有四个函数可以使用,分别是:</p>
<p data-tool="mdnice编辑器"><strong>Set()</strong></p>
<pre data-tool="mdnice编辑器"><code>func&nbsp;(c&nbsp;*cache)&nbsp;Set(k&nbsp;string,&nbsp;x&nbsp;interface{},&nbsp;d&nbsp;time.Duration)&nbsp;{<br>&nbsp;var&nbsp;e&nbsp;int64<br>&nbsp;if&nbsp;d&nbsp;==&nbsp;DefaultExpiration&nbsp;{<br>&nbsp;&nbsp;d&nbsp;=&nbsp;c.defaultExpiration<br>&nbsp;}<br>&nbsp;if&nbsp;d&nbsp;&gt;&nbsp;0&nbsp;{<br>&nbsp;&nbsp;e&nbsp;=&nbsp;time.Now().Add(d).UnixNano()&nbsp;&nbsp;&nbsp;//&nbsp;过期时间,绝对时间<br>&nbsp;}<br>&nbsp;c.mu.Lock()<br>&nbsp;c.items&nbsp;=&nbsp;Item{<br>&nbsp;&nbsp;Object:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x,<br>&nbsp;&nbsp;Expiration:&nbsp;e,&nbsp;<br>&nbsp;}<br>&nbsp;c.mu.Unlock()<br>}<br></code></pre>
<p data-tool="mdnice编辑器">函数的逻辑还是比较简单的,需要注意的是过期时间是绝对时间。</p>
<p data-tool="mdnice编辑器"><strong>SetDefault()</strong></p>
<pre data-tool="mdnice编辑器"><code>func&nbsp;(c&nbsp;*cache)&nbsp;SetDefault(k&nbsp;string,&nbsp;x&nbsp;interface{})&nbsp;{<br>&nbsp;c.Set(k,&nbsp;x,&nbsp;DefaultExpiration)<br>}<br></code></pre>
<p data-tool="mdnice编辑器">SetDefault() 内部调用了 Set() 函数实现,使用默认过期时间。</p>
<p data-tool="mdnice编辑器"><strong>Add()</strong>如果待添加的 K 不存在的话才能添加成功,包括已过期但还未被删除,否则返回错误。</p>
<p data-tool="mdnice编辑器"><strong>Replace()</strong></p>
<p data-tool="mdnice编辑器">如果待添加的 K 已存在的话才能执行成功,否则返回错误。操作条件真好与 Add() 相反。</p>
<p data-tool="mdnice编辑器">上面两个函数逻辑比较简单就不放源码,大家自行查看。</p>
<h3 data-tool="mdnice编辑器">删除</h3>
<p data-tool="mdnice编辑器">删除操作主要有两个,执行删除操作的时候都会判断是否需要执行删除回调函数,下面我们可以看到。</p>
<p data-tool="mdnice编辑器"><strong>Delete()</strong>&nbsp;常规删除,不管是否过期都会删除。</p>
<p data-tool="mdnice编辑器"><strong>DeleteExpired()</strong>&nbsp;用于执行批量删除操作,只会删除已过期的键值对。</p>
<pre data-tool="mdnice编辑器"><code>func&nbsp;(c&nbsp;*cache)&nbsp;Delete(k&nbsp;string)&nbsp;{<br>&nbsp;c.mu.Lock()<br>&nbsp;v,&nbsp;evicted&nbsp;:=&nbsp;c.delete(k)&nbsp;//&nbsp;v&nbsp;是&nbsp;k&nbsp;对应的值<br>&nbsp;c.mu.Unlock()<br>&nbsp;if&nbsp;evicted&nbsp;{&nbsp;&nbsp;&nbsp;//&nbsp;是否需要执行删除回调函数<br>&nbsp;&nbsp;c.onEvicted(k,&nbsp;v)<br>&nbsp;}<br>}<br><br>func&nbsp;(c&nbsp;*cache)&nbsp;delete(k&nbsp;string)&nbsp;(interface{},&nbsp;bool)&nbsp;{<br>&nbsp;if&nbsp;c.onEvicted&nbsp;!=&nbsp;nil&nbsp;{<br>&nbsp;&nbsp;if&nbsp;v,&nbsp;found&nbsp;:=&nbsp;c.items;&nbsp;found&nbsp;{<br>&nbsp;&nbsp;&nbsp;delete(c.items,&nbsp;k)<br>&nbsp;&nbsp;&nbsp;return&nbsp;v.Object,&nbsp;true<br>&nbsp;&nbsp;}<br>&nbsp;}<br>&nbsp;//&nbsp;能执行到这里说明有两种情况:<br>&nbsp;//&nbsp;1.没有设置删除回调函数&nbsp;2.设置了删除回调函数,但是键值对不存在<br>&nbsp;delete(c.items,&nbsp;k)<br>&nbsp;return&nbsp;nil,&nbsp;false<br>}<br><br>//&nbsp;将所有过期的键值对从缓存中删除,如果设置有回调函数,则会将键值对作为参数调用回调函数<br>func&nbsp;(c&nbsp;*cache)&nbsp;DeleteExpired()&nbsp;{<br>&nbsp;var&nbsp;evictedItems&nbsp;[]keyAndValue&nbsp;&nbsp;//&nbsp;临时存放已经过期的键值对<br>&nbsp;now&nbsp;:=&nbsp;time.Now().UnixNano()<br>&nbsp;c.mu.Lock()<br>&nbsp;for&nbsp;k,&nbsp;v&nbsp;:=&nbsp;range&nbsp;c.items&nbsp;{<br>&nbsp;&nbsp;if&nbsp;v.Expiration&nbsp;&gt;&nbsp;0&nbsp;&amp;&amp;&nbsp;now&nbsp;&gt;&nbsp;v.Expiration&nbsp;{&nbsp;&nbsp;//&nbsp;是否已经过期<br>&nbsp;&nbsp;&nbsp;ov,&nbsp;evicted&nbsp;:=&nbsp;c.delete(k)<br>&nbsp;&nbsp;&nbsp;if&nbsp;evicted&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;是否需要执行删除回调函数<br>&nbsp;&nbsp;&nbsp;&nbsp;evictedItems&nbsp;=&nbsp;append(evictedItems,&nbsp;keyAndValue{k,&nbsp;ov})<br>&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}<br>&nbsp;}<br>&nbsp;c.mu.Unlock()<br>&nbsp;for&nbsp;_,&nbsp;v&nbsp;:=&nbsp;range&nbsp;evictedItems&nbsp;{<br>&nbsp;&nbsp;c.onEvicted(v.key,&nbsp;v.value)&nbsp;&nbsp;//&nbsp;执行删除回调函数<br>&nbsp;}<br>}<br><br></code></pre>
<p data-tool="mdnice编辑器">关于删除回调函数的详细用法,请关注公众号【Golang来啦】,后台发送关键字 gocache 获取。</p>
<div class="appmsg_card_context wx_profile_card wx-root wx_tap_card common-webchat">
<div class="wx_profile_card_inner">
<div class="wx_profile_card_bd">
<div class="wx_profile weui-flex">
<div class="wx_profile_hd"><img alt="" class="wx_profile_avatar lazyload" data-src="http://mmbiz.qpic.cn/mmbiz_png/zVM9HMBJAjPLZsNAwoP93y3gJtYPibvKcZWoRVVBW1kGaZ6RL41uL1TaoPGwsc4ohmmIoameiaYrficRo6b9EExrg/0?wx_fmt=png"></div>
<div class="wx_profile_bd weui-flex weui-flex__item">
<div class="weui-flex__item"><span class="wx_profile_nickname"><span class="wx_profile_nickname">Golang来啦</span></span>
<div id="js_a11y_wx_profile_desc" class="wx_profile_desc">欢迎来到Gopher自留地! 专注于分享Golang知识、职场心得和生活感悟。带你入门,完成进阶,顺利掌握Golang ! Golang已来,上车!!</div>
<div id="js_a11y_wx_profile_tips" class="wx_profile_tips"><span class="wx_profile_tips_meta">205篇原创内容</span></div>
</div>
</div>
</div>
</div>
<div id="js_a11y_wx_profile_logo" class="wx_profile_card_ft">公众号</div>
</div>
</div>
<h3 data-tool="mdnice编辑器">备份恢复数据</h3>
<p data-tool="mdnice编辑器">虽然 go-cache 比较倾向于当做缓存数据来使用,但还是提供了备份数据和恢复数据的操作,数据使用 gob 序列化。</p>
<p data-tool="mdnice编辑器">详细用法请关注公众号【Golang来啦】,后台发送关键字 gocache 获取。</p>
<h3 data-tool="mdnice编辑器">其他一些操作</h3>
<p data-tool="mdnice编辑器">其他一些操作包括:</p>
<ul class="list-paddingleft-1" data-tool="mdnice编辑器">
<li>ItemCount(),返回所有数据的条数,这里的条数包括已过期但还未被删除的数量;</li>
<li>Flush(),清空数据;</li>
<li>Items(),返回数据的未过期的数据,可以使用 NewFrom() 恢复数据;</li>
</ul>
<p data-tool="mdnice编辑器">从各种操作的源码就可以看出,go-cache 内部通过使用读写锁(sync.RWMutex)保证并发安全。</p>
<h3 data-tool="mdnice编辑器">优雅地关闭 goroutine</h3>
<p data-tool="mdnice编辑器">这个问题之前有提到过,也是一种优雅停止 goroutine 的一种方式。我们把相关的代码拉出来看下:</p>
<pre data-tool="mdnice编辑器"><code>func&nbsp;newCacheWithJanitor(de&nbsp;time.Duration,&nbsp;ci&nbsp;time.Duration,&nbsp;m&nbsp;mapItem)&nbsp;*Cache&nbsp;{<br>&nbsp;c&nbsp;:=&nbsp;newCache(de,&nbsp;m)&nbsp;&nbsp;&nbsp;//&nbsp;实例化cache<br>&nbsp;C&nbsp;:=&nbsp;&amp;Cache{c}<br>&nbsp;if&nbsp;ci&nbsp;&gt;&nbsp;0&nbsp;{<br>&nbsp;&nbsp;runJanitor(c,&nbsp;ci)&nbsp;&nbsp;&nbsp;//&nbsp;开启清理器<br>&nbsp;&nbsp;runtime.SetFinalizer(C,&nbsp;stopJanitor)&nbsp;&nbsp;&nbsp;//&nbsp;指定调用函数停止后台&nbsp;goroutine<br>&nbsp;}<br>&nbsp;return&nbsp;C<br>}<br><br>func&nbsp;stopJanitor(c&nbsp;*Cache)&nbsp;{<br>&nbsp;c.janitor.stop&nbsp;&lt;-&nbsp;true<br>}<br><br>type&nbsp;janitor&nbsp;struct&nbsp;{&nbsp;//&nbsp;清理器结构体<br>&nbsp;Interval&nbsp;time.Duration&nbsp;//&nbsp;清理时间间隔<br>&nbsp;stop&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;chan&nbsp;bool&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;是否停止<br>}<br><br>func&nbsp;(j&nbsp;*janitor)&nbsp;Run(c&nbsp;*cache)&nbsp;{<br>&nbsp;ticker&nbsp;:=&nbsp;time.NewTicker(j.Interval)&nbsp;&nbsp;//&nbsp;开定时器<br>&nbsp;for&nbsp;{<br>&nbsp;&nbsp;select&nbsp;{<br>&nbsp;&nbsp;case&nbsp;&lt;-ticker.C:<br>&nbsp;&nbsp;&nbsp;c.DeleteExpired()&nbsp;&nbsp;&nbsp;//&nbsp;定时调用&nbsp;DeleteExpired()&nbsp;执行过期删除操作<br>&nbsp;&nbsp;case&nbsp;&lt;-j.stop:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;接收到停止清理器的信号,下面便停止定时器并返回,退出协程<br>&nbsp;&nbsp;&nbsp;ticker.Stop()<br>&nbsp;&nbsp;&nbsp;return<br>&nbsp;&nbsp;}<br>&nbsp;}<br>}<br><br>//&nbsp;运行清理器<br>func&nbsp;runJanitor(c&nbsp;*cache,&nbsp;ci&nbsp;time.Duration)&nbsp;{<br>&nbsp;j&nbsp;:=&nbsp;&amp;janitor{&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;实例化清理器<br>&nbsp;&nbsp;Interval:&nbsp;ci,<br>&nbsp;&nbsp;stop:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;make(chan&nbsp;bool),<br>&nbsp;}<br>&nbsp;c.janitor&nbsp;=&nbsp;j<br>&nbsp;go&nbsp;j.Run(c)&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;开协程,定时清理器跑起来!!<br>}<br><br></code></pre>
<p data-tool="mdnice编辑器">这里退出后台 goroutine 使用的是下面这个函数,当 GC 准备释放对象时,会调用该函数指定的方法。</p>
<pre data-tool="mdnice编辑器"><code>runtime.SetFinalizer(obj,func(obj&nbsp;*typeObj))<br></code></pre>
<p data-tool="mdnice编辑器">当我们取消对 C 对象的引用时,如果不退出 runJanitor() 开启的 goroutine 就会造成内存泄漏。当 gc 准备释放 C 时,会调用指定函数 stopJanitor(),Run() 方法便能收到信号,退出协程,gc 也会将 c 释放掉。</p>
<h3 data-tool="mdnice编辑器">总结</h3>
<p data-tool="mdnice编辑器">go-cache 的源码结构清晰,代码量也不多,还能掌握一种优雅退出 goroutine 的防止,推荐阅读,有问题可以发信息给我,相互学习进步!</p>
<p data-tool="mdnice编辑器">项目地址:https://github.com/patrickmn/go-cache</p><br><br>
来源:https://www.cnblogs.com/cheyunhua/p/16802281.html
頁: [1]
查看完整版本: Go 每日一库之 go-cache 缓存