Maison  >  Article  >  développement back-end  >  Exemple pour expliquer l'implémentation de la simulation Golang du sémaphore avec délai d'attente

Exemple pour expliquer l'implémentation de la simulation Golang du sémaphore avec délai d'attente

巴扎黑
巴扎黑original
2017-09-07 10:09:552090parcourir

Cet article vous présente principalement les informations pertinentes sur l'implémentation de la simulation de sémaphores avec délais d'attente par Golang. L'article le présente en détail à travers des exemples de codes. Il a une certaine valeur d'apprentissage de référence pour les études ou le travail de tous les amis qui en ont besoin. avec l'éditeur ci-dessous.

Avant-propos

J'écris un projet récemment et je dois utiliser un sémaphore pour attendre que certaines ressources soient terminées, mais l'attente maximale est N millisecondes. Avant d'aborder le texte principal de cet article, examinons d'abord la méthode d'implémentation en langage C.

En langage C, il existe l'API suivante pour implémenter l'attente de sémaphore avec timeout :


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

Puis dans Après vérification le document golang, j'ai trouvé qu'il n'y a pas de sémaphore avec timeout implémenté dans golang. Le document officiel est ici.

Principe

Mon scénario métier est le suivant : J'ai un dictionnaire de cache, lorsque plusieurs utilisateurs demandent une clé inexistante à ce moment-là, une seule requête pénétrera dans le backend, et tous les utilisateurs devront faire la queue et attendre que cette requête soit terminée, ou expirer et revenir.

Comment y parvenir ? En fait, si vous réfléchissez un instant au principe de cond, vous pouvez simuler une cond avec timeout.

Dans Golang, pour implémenter « suspension en attente » et « retour de délai d'attente » en même temps, vous devez généralement utiliser la syntaxe de sélection de cas. Un cas attend les ressources bloquées et un cas attend une minuterie. est très sûr.

Les ressources initialement bloquées doivent être notifiées de l'achèvement via le mécanisme des variables de condition Puisqu'il est décidé d'utiliser le cas de sélection ici, il est naturel de penser à utiliser le canal pour remplacer cette notification d'achèvement.

Le problème suivant est que de nombreux demandeurs sont envoyés simultanément pour obtenir cette ressource, mais la ressource n'est pas encore prête, donc tout le monde doit faire la queue et se suspendre, en attendant que la ressource soit terminée, et avertir tout le monde lorsque le la ressource est terminée.

Il est donc naturel de créer une file d'attente pour cette ressource. Chaque demandeur crée un chan, met le chan dans la file d'attente, puis sélectionne le cas pour attendre la notification de ce chan. À l'autre extrémité, une fois la ressource terminée, elle parcourt la file d'attente et informe chaque canal.

Le dernier problème est que seul le premier demandeur peut pénétrer la requête vers le backend, et les demandeurs suivants ne doivent pas pénétrer les requêtes répétées. Cela peut être déterminé en jugeant s'il y a cette clé dans le cache pour la première fois. . condition et bit d'indicateur init pour déterminer si le demandeur doit faire la queue.

Mon scénario

Ce qui précède est l'idée, et ce qui suit est la mise en œuvre de mon scénario commercial.


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
}

Décrivez brièvement l'ensemble du processus :

  • Verrouillez d'abord le dictionnaire, si la clé n'existe pas, indiquant que je suis le premier demandeur, et je vais créer la valeur correspondant à cette clé, mais init=false signifie qu'elle est en cours d'initialisation. Enfin, libérez le verrou du dictionnaire.

  • Ensuite, verrouillez la clé, jugez qu'elle a été initialisée, puis renvoyez directement la valeur. Sinon, créez un chan et placez-le dans la file d'attente waitQueue. Enfin, relâchez le verrouillage des touches.

  • Ensuite, s'il s'agit du premier demandeur, il pénétrera la requête jusqu'au backend (initiera un appel réseau dans une coroutine indépendante).

  • Maintenant, créez une minuterie pour le délai d'attente.

  • Enfin, qu'il s'agisse du premier demandeur de clé ou d'un demandeur simultané lors de l'initialisation, ils sont tous complétés en attendant le résultat du délai d'expiration du cas de sélection.

Dans la fonction initCacheItem, les données ont été obtenues avec succès


 // 一旦标记为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)
 }
  • Tout d'abord, verrouillez la clé, marquez init=true, attribuez une valeur et relâchez le verrou. Les demandes ultérieures peuvent être renvoyées immédiatement sans file d'attente. Après

  • , comme init=true a été marqué, il n'y a plus de demandes de modification de waitQueue pour le moment, il n'est donc pas nécessaire de verrouiller, de parcourir directement la file d'attente et de notifier chacun chan dedans.

Enfin

Cela permet d'obtenir l'effet de variable de condition avec timeout. En fait, ma scène est un exemple de diffusion Cond. , vous pouvez vous référer aux idées pour obtenir l'effet souhaité, l'apprendre et l'utiliser.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn