慎用unsigned int减法


最近在做一个缓存的清理策略时,遇到了一个诡异的bug。

缓存每次命中,都会更新一个时间戳;然后定期清理的时候,用当前时间now减去缓存的时间戳,如果差值大于7天,则清理掉这个缓存项。

1
2
3
4
5
uint32_t now = time();
... // Some other code
if (now - hash_elem.timestamp > expire_interval) {
erase(hash_elem);
}

逻辑很简单,刚开始单线程运行也没有问题,但是在我对整个程序进行完多线程改造后,从log中发现,越是经常命中的缓存项,就越是容易过期。这和常识是相悖的,按理说,应该越常见的缓存项,则timestamp更新的越频繁,也就越不容易过期才对。

难道是多线程并发写,把timestamp写坏了?

我又打印出了各个写timestamp处的值,从日志看,timestamp的数值正常。

接下来,我又把now的值打出来看,看上去也是正常的一个时间戳。

但是,仔细一看,now的值居然比hash_elem.timestamp的值要小!

原来这里是因为在多线程环境下,获取了now的值之后,hash_elem.timestamp的值又被其他线程更新了。

而当两个unsigned int相减得到一个负数时,在计算机中的表示,符号位(第1位)为1。此时如果按照unsigned int来解释,就将得到一个非常大的数字,自然大于expire_interval,也就会导致

越是频繁命中的缓存项,越容易过期

的现象。


解决这个问题两种方式:

  1. 强制类型转换,改成 int(now - hash_elem.timestamp) > expire_interval
  2. 使用加法替换减法,改成 now > hash_elem.timestamp + expire_interval,不过这里要考虑加法的溢出问题。

使用unsigned int相减时,要小心。



推荐阅读:
使用双buffer无锁化
不要拷贝
读写锁的性能一定更好吗

转载请注明出处: http://blog.guoyb.com/2018/10/27/uint-minus/

欢迎使用微信扫描下方二维码,关注我的微信公众号TechTalking,技术·生活·思考:
后端技术小黑屋

Comments