Heim  >  Artikel  >  Backend-Entwicklung  >  Beispiel zur Erläuterung der Golang-Simulationsimplementierung von Semaphor mit Timeout

Beispiel zur Erläuterung der Golang-Simulationsimplementierung von Semaphor mit Timeout

巴扎黑
巴扎黑Original
2017-09-07 10:09:552005Durchsuche

Dieser Artikel stellt Ihnen hauptsächlich die relevanten Informationen zur Simulationsimplementierung von Semaphoren mit Zeitüberschreitungen vor. Der Artikel stellt sie ausführlich anhand von Beispielcodes vor. Es hat einen gewissen Referenz-Lernwert für alle, die es brauchen. Lasst uns lernen mit dem Herausgeber unten.

Vorwort

Ich schreibe kürzlich ein Projekt und muss ein Semaphor verwenden, um auf den Abschluss einiger Ressourcen zu warten, aber die maximale Wartezeit beträgt N Millisekunden. Bevor wir uns den Haupttext dieses Artikels ansehen, werfen wir zunächst einen Blick auf die Implementierungsmethode in der C-Sprache.

In der C-Sprache gibt es die folgende API zum Implementieren des Semaphorwartens mit Timeout:


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

Dann in Nach der Überprüfung Im Golang-Dokument habe ich festgestellt, dass in Golang kein Semaphor mit Timeout implementiert ist. Das offizielle Dokument ist hier.

Prinzip

Mein Geschäftsszenario ist folgendes: Ich habe ein Cache-Wörterbuch, wenn mehrere Benutzer einen nicht vorhandenen Schlüssel anfordern. Zu diesem Zeitpunkt Nur eine Anfrage dringt in das Backend ein und alle Benutzer müssen sich anstellen und warten, bis diese Anfrage abgeschlossen ist, oder eine Zeitüberschreitung eintreten und zurückkehren.

Wie erreicht man das? Wenn Sie einen Moment über das Prinzip von cond nachdenken, können Sie tatsächlich ein cond mit Timeout simulieren.

Um in Golang gleichzeitig „Warten anhalten“ und „Timeout-Rückgabe“ zu implementieren, muss ein Fall auf blockierte Ressourcen und ein Fall auf einen Timer warten ist sehr sicher.

Ursprünglich blockierte Ressourcen sollten über den Mechanismus der Bedingungsvariablen über den Abschluss informiert werden. Da hier die Auswahl von Groß- und Kleinschreibung gewählt wurde, ist es naheliegend, über die Verwendung eines Kanals nachzudenken, um diese Abschlussbenachrichtigung zu ersetzen.

Das nächste Problem besteht darin, dass viele Anforderer gleichzeitig gesendet werden, um diese Ressource zu erhalten, die Ressource jedoch noch nicht bereit ist. Daher müssen alle in der Warteschlange stehen und warten, bis die Ressource abgeschlossen ist, und alle benachrichtigen, wenn die Ressource abgeschlossen ist Ressource ist abgeschlossen.

Daher ist es selbstverständlich, eine Warteschlange für diese Ressource zu erstellen. Jeder Anforderer erstellt einen Kanal, fügt den Kanal in die Warteschlange ein und wählt dann den Fall aus, um auf die Benachrichtigung dieses Kanals zu warten. Am anderen Ende durchläuft sie nach Abschluss der Ressource die Warteschlange und benachrichtigt jeden Kanal.

Das letzte Problem besteht darin, dass nur der erste Anforderer die Anfrage zum Backend durchdringen kann und nachfolgende Anforderer keine wiederholten Anfragen durchdringen sollten. Dies kann festgestellt werden, indem beurteilt wird, ob sich dieser Schlüssel beim ersten Mal im Cache befindet . Bedingung und Flag-Bit init, um zu bestimmen, ob der Anforderer in die Warteschlange gestellt werden soll.

Mein Szenario

Das Obige ist die Idee und das Folgende ist die Umsetzung meines Geschäftsszenarios.


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
}

Beschreiben Sie kurz den gesamten Vorgang:

  • Sperren Sie zunächst das Wörterbuch, falls vorhanden existiert nicht, was darauf hinweist, dass ich der erste Anforderer bin und den diesem Schlüssel entsprechenden Wert erstellen werde, aber init=false bedeutet, dass er initialisiert wird. Geben Sie abschließend die Wörterbuchsperre frei.

  • Als nächstes sperren Sie den Schlüssel, beurteilen, ob er initialisiert wurde, und geben dann den Wert direkt zurück. Andernfalls erstellen Sie einen Kanal und stellen Sie ihn in die Warteschlange „waitQueue“. Lösen Sie abschließend die Tastensperre.

  • Als nächstes wird, wenn es der erste Anforderer ist, die Anfrage zum Backend durchdringen (initiieren Sie einen Netzwerkaufruf in einer unabhängigen Coroutine).

  • Erstellen Sie nun einen Timer für die Auszeit.

  • Unabhängig davon, ob es sich um den ersten Anforderer des Schlüssels oder einen gleichzeitigen Anforderer während der Initialisierung handelt, werden sie alle abgeschlossen, indem auf das Ergebnis des Select-Case-Timeouts gewartet wird.

In der initCacheItem-Funktion wurden die Daten erfolgreich abgerufen


 // 一旦标记为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)
 }
  • Zuerst sperren Geben Sie den Schlüssel ein, markieren Sie init=true, weisen Sie einen Wert zu und geben Sie die Sperre frei. Nachfolgende Anfragen können sofort und ohne Warteschlangen zurückgegeben werden. Nach

  • gibt es zu diesem Zeitpunkt keine weiteren Anforderungen zum Ändern von waitQueue, da init=true markiert wurde, sodass keine Notwendigkeit besteht, die Warteschlange zu sperren, direkt zu durchlaufen und zu benachrichtigen jeder Chan darin.

Endlich

Dadurch wird der Bedingungsvariableneffekt mit Timeout erreicht. Tatsächlich handelt es sich bei meiner Szene um ein Broadcast-Cond-Beispiel , Sie können sich auf die Ideen beziehen, um den gewünschten Effekt zu erzielen, sie lernen und nutzen.

Das obige ist der detaillierte Inhalt vonBeispiel zur Erläuterung der Golang-Simulationsimplementierung von Semaphor mit Timeout. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn