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!