当前位置: 首页 > 编程笔记 >

golang模拟实现带超时的信号量示例代码

夏侯弘光
2023-03-14
本文向大家介绍golang模拟实现带超时的信号量示例代码,包括了golang模拟实现带超时的信号量示例代码的使用技巧和注意事项,需要的朋友参考一下

前言

最近在写项目,需要用到信号量等待一些资源完成,但是最多等待N毫秒。在看本文的正文之前,我们先来看下C语言里的实现方法。

在C语言里,有如下的API来实现带超时的信号量等待:

SYNOPSIS
  #include <pthread.h>
 
  int
  pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

然后在查看golang的document后,发现golang里并没有实现带超时的信号量,官方文档在这里。

原理

我的业务场景是这样的:我有一个缓存字典,当多个用户请求1个不存在的key时,只有1个请求会穿透到后端,而所有用户都要排队等这个请求完成,或者超时返回。

怎么实现呢?其实稍微想一想cond的原理,就能模拟一个带超时的cond出来。

在golang里,要同时实现”挂起等待”和”超时返回”,一般得用select case语法,一个case等待阻塞的资源,一个case等待一个timer,这一点是非常确定的。

原本阻塞的资源应该通过条件变量的机制来实现完成通知,既然这里决定用select case,那么自然想到用channel来代替这个完成通知。

接下来的问题就是,很多请求者并发来获取这个资源,但是资源还没有准备好,所以大家都要排队并挂起,等待资源完成,并且当资源完成后通知大家。

所以,这里很自然要为这个资源做一个队列,每个请求者创建一个chan,并将chan放到队列里,接着select case等待这个chan的通知。而另一端,资源完成后遍历队列,通知每个chan即可。

最后一个问题是,只有第一个请求者才能穿透请求到后端,而后续请求者不应该穿透重复的请求,这可以通过判断缓存里是否有这个key作为判定首次的条件,而标记位init来判断请求者是否应该排队。

我的场景

上面是思路,下面是我的业务场景实现。

func (cache *Cache) Get(key string, keyType int) *string {
 if keyType == KEY_TYPE_DOMAIN {
 key = "#" + key
 } else {
 key = "=" + key
 }
 
 cache.mutex.Lock()
 item, existed := cache.dict[key]
 if !existed {
 item = &cacheItem{}
 item.key = &key
 item.waitQueue = list.New()
 cache.dict[key] = item
 }
 cache.mutex.Unlock()
 
 conf := config.GetConfig()
 
 lastGet := getCurMs()
 
 item.mutex.Lock()
 item.lastGet = lastGet
 if item.init { // 已存在并且初始化
 defer item.mutex.Unlock()
 return item.value
 }
 
 // 未初始化,排队等待结果
 wait := waitItem{}
 wait.wait_chan = make(chan *string, 1)
 item.waitQueue.PushBack(&wait)
 item.mutex.Unlock()
 
 // 新增key, 启动goroutine获取初始值
 if !existed {
 go cache.initCacheItem(item, keyType)
 }
 
 timer := time.NewTimer(time.Duration(conf.Cache_waitTime) * time.Millisecond)
 
 var retval *string = nil
 
 // 等待初始化完成
 select {
 case retval = <- wait.wait_chan:
 case <- timer.C:
 }
 return retval
}

简述一下整个过程:

  • 首先锁字典,如果key不存在,说明我是第一个请求者,我会创建这个key对应的value,只不过init=false表示它正在初始化。最后,释放字典锁。
  • 接下来,锁住这个key,判断它已经初始化完成,那么直接返回value。否则,创建一个chan放入waitQueue等待队列。最后,释放key锁。
  • 接着,如果当前是第一个请求者,那么会穿透请求到后端(在一个独立的协程里去发起网络调用)。
  • 现在,创建一个用于超时的定时器。
  • 最后,无论当前是否是key的第一个请求者,还是初始化期间的并发请求者,它们都通过select case超时的等待结果完成。

在initCacheItem函数里,数据已获取成功

 // 一旦标记为init, 后续请求将不再操作waitQueue
 item.mutex.Lock()
 item.value = newValue
 item.init = true
 item.expire = expire
 item.mutex.Unlock()
 
 // 唤醒所有排队者
 waitQueue := item.waitQueue
 for elem := waitQueue.Front(); elem != nil; elem = waitQueue.Front() {
 wait := elem.Value.(*waitItem)
 wait.wait_chan <- newValue
 waitQueue.Remove(elem)
 }
  • 首先,锁住key,标记init=true,并赋值value,并释放锁。此后的请求,都可以立即返回,无需排队。
  • 之后,因为init=true已被标记,此刻再也有没有请求会修改waitQueue,所以无需加锁,直接遍历队列,通知其中的每个chan。

最后

这样就实现了带超时的条件变量效果,实际上我的场景是一个broadcast的cond例子,大家可以参照思路实现自己想要的效果,活学活用。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对小牛知识库的支持。

 类似资料:
  • 本文向大家介绍golang使用信号量热更新的实现示例,包括了golang使用信号量热更新的实现示例的使用技巧和注意事项,需要的朋友参考一下 配置文件热更新是服务器程序的一个基本功能,通过热更新可以不停机调整程序的配置,特别是在生产环境可以提供极大的便利,比如发现log打得太多了可以动态调高日志等级,业务逻辑参数变化,甚至某个功能模块的开关等都可以动态调整。 以上就是本文的全部内容,希望对大家的学习

  • 问题内容: 谁能分享使用信号量的简单示例?如果可能的话,可以在没有信号量的情况下解决任务,然后再通过信号量来了解任务的主要思想。 问题答案: 这是一个简单的信号量实现: 该方法发送一个信号,该信号内部存储在信号量中。该方法等待信号。接收到信号标志后,再次将其清除,然后退出该方法。 阅读本文并看一下这个例子

  • 本文向大家介绍Python实现TCP通信的示例代码,包括了Python实现TCP通信的示例代码的使用技巧和注意事项,需要的朋友参考一下 使用socket实现tcp通信,需导入socket模块 1、服务端 主要步骤: (1)创建socket:socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None) family:AF_

  • 本文向大家介绍Android实现实时通信示例,包括了Android实现实时通信示例的使用技巧和注意事项,需要的朋友参考一下 我们今天来聊下如何做实时通讯(先给知识点,实现原理,最后给出实现实时通信的具体代码--使用工具 android studio) 现在先说下用到的知识点: java的socket包括服务端和客户端,  这个是用来实现通信 java的多线程,这个是用来实现实时通信 java的线程

  • 本文向大家介绍Java 信号量Semaphore的实现,包括了Java 信号量Semaphore的实现的使用技巧和注意事项,需要的朋友参考一下 近日于LeetCode看题遇1114 按序打印,获悉一解法使用了Semaphore,顺势研究,记心得于此。 此解视Semaphore为锁,以保证同一时刻单线程的顺序执行。在此原题上,我作出如下更改。 10个线程的常量池中,分别调用R1,R2,R3的方法多次

  • 本文向大家介绍three.js实现3D模型展示的示例代码,包括了three.js实现3D模型展示的示例代码的使用技巧和注意事项,需要的朋友参考一下 由于项目需要展示3d模型,所以对three做了点研究,分享出来 希望能帮到大家 先看看效果:   three.js整体来说 不是很难 只要你静下心来研究研究 很快就会上手的 首先我们在页面上需要创建一个能够放置3D模型的画布 也可以说是初始化 Thre