この記事では、主に 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ドキュメントを確認したところ、golangはタイムアウト付きセマフォを実装していないことがわかりました。公式ドキュメントはここにあります。
原則
私のビジネスシナリオは次のとおりです: 複数のユーザーが存在しないキーをリクエストした場合、1つのリクエストだけがバックエンドに到達し、すべてのユーザーはキューに入れて、このリクエストが完了するまで待つか、タイムアウト後に戻ります。
それを達成するにはどうすればよいですか?実際、cond の原理を少し考えてみると、タイムアウトを伴う cond をシミュレートできます。
golang では、「一時停止待機」と「タイムアウト復帰」を同時に実装するには、通常、select case 構文を使用する必要があります。1 つのケースはブロックされたリソースを待機し、もう 1 つのケースはタイマーを待機します。 。
本来ブロックされているリソースは条件変数の仕組みを通じて完了通知されるべきなので、ここではselect caseを使うことにしたので、当然この完了通知をチャネルで置き換えることを考えました。
次の問題は、多くの要求者が同時にこのリソースを取得するためにやって来ますが、リソースはまだ準備ができていないため、全員がキューに並んで電話を切り、リソースが完了するのを待ち、リソースが完了したら全員に通知する必要があることです。
したがって、各リクエスターがチャンを作成し、そのチャンをキューに入れ、このチャンの通知を待つケースを選択するのは自然なことです。一方、リソースが完了すると、キューを横断して各 chan に通知します。
最後の問題は、最初のリクエスタのみがバックエンドへのリクエストを通過でき、後続のリクエスタは繰り返しのリクエストを通過できないことです。これは、最初の条件としてキャッシュにこのキーがあるかどうか、およびフラグ ビットを判断することで判断できます。 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 }
プロセス全体を簡単に説明します。
まず、キーが存在しない場合は、私がこのキーに対応する値を作成することを意味します。 init=false 初期化中であることを示します。最後に辞書ロックを解除します。
次にキーをロックして初期化されたと判断し、値を直接返します。それ以外の場合は、chan を作成し、waitQueue 待機キューに入れます。最後にキーロックを解除してください。
その後、それが最初のリクエスターである場合、リクエストはバックエンドにパススルーされます (独立したコルーチンでネットワーク呼び出しを開始します)。
次に、タイムアウト用のタイマーを作成します。
最後に、キーの最初のリクエスターであるか、初期化中の同時リクエスターであるかに関係なく、選択ケースのタイムアウトの結果を待つことですべてが完了します。
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) }
まず、キーをロックし、init=trueとしてマークし、値を割り当て、ロックを解放します。後続のリクエストはキューに入れずにすぐに返すことができます。
その後、init=true がマークされているため、現時点では waitQueue を変更するリクエストはなく、ロックする必要はなく、キューを直接走査し、その中の各 chan に通知します。
最後に
このようにして、タイムアウト付きの条件変数エフェクトが実現されます。実際、私のシーンは、希望するエフェクトを実現するためのアイデアを参照して学習して使用できます。それ。
以上がタイムアウト付きセマフォのGolangシミュレーション実装を説明する例の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。