이 글은 주로 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은 시간 제한이 있는 세마포어를 구현하지 않는다는 것을 발견했습니다. 공식 문서는 다음과 같습니다.
Principle
내 비즈니스 시나리오는 다음과 같습니다. 여러 사용자가 존재하지 않는 키를 요청하면 단 1개의 요청만 백엔드에 침투하고 모든 사용자는 대기열에 들어가야 합니다. 이 요청이 완료될 때까지 기다리거나 시간 초과 후 반환하세요.
어떻게 달성하나요? 사실 cond의 원리를 잠시 생각해 보면 timeout이 있는 cond를 시뮬레이션할 수 있습니다.
golang에서 "일시 중지 대기"와 "시간 초과 반환"을 동시에 구현하려면 일반적으로 선택 케이스 구문을 사용해야 합니다. 한 케이스는 차단된 리소스를 기다리고, 다른 케이스는 타이머를 기다립니다. .
원래 차단된 리소스는 조건 변수의 메커니즘을 통해 완료를 알려야 합니다. 여기서는 선택 사례를 사용하기로 결정했기 때문에 자연스럽게 채널을 사용하여 이 완료 알림을 대체하는 것을 생각했습니다.
다음 문제는 많은 요청자가 이 리소스를 동시에 얻으려고 왔지만 리소스가 아직 준비되지 않았기 때문에 모두가 대기열에 서서 리소스가 완료될 때까지 기다리고 리소스가 완료되면 모든 사람에게 알려야 한다는 것입니다.
그래서 이 리소스에 대한 대기열을 만드는 것은 당연합니다. 각 요청자는 채널을 생성하고 해당 채널을 대기열에 넣은 다음 이 채널의 알림을 기다릴 케이스를 선택합니다. 다른 한편으로는 리소스가 완료된 후 대기열을 순회하여 각 채널에 알립니다.
마지막 문제는 첫 번째 요청자만이 백엔드에 요청을 침투할 수 있고, 후속 요청자는 반복되는 요청을 침투해서는 안 된다는 점입니다. 이는 첫 번째 조건으로 캐시에 이 키가 있는지 여부와 플래그 비트로 판단할 수 있습니다. 요청자가 대기열에 있어야 하는지 여부를 결정하기 위해 초기화합니다.
내 시나리오
위는 아이디어이고, 다음은 내 비즈니스 시나리오의 구현입니다.
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 초기화 중임을 나타냅니다. 마지막으로 사전 잠금을 해제합니다.
다음에는 키를 잠그고 초기화되었다고 판단한 후 직접 값을 반환합니다. 그렇지 않으면 채널을 생성하여 waitQueue 대기 대기열에 넣습니다. 마지막으로 키 잠금을 해제합니다.
그런 다음 첫 번째 요청자인 경우 요청을 백엔드로 전달합니다(독립 코루틴에서 네트워크 호출 시작).
이제 타임아웃 타이머를 만들어보세요.
마지막으로 초기화 시 키의 첫 번째 요청자이든 동시 요청자이든 상관없이 Select Case Timeout 결과를 기다리면 모두 완료됩니다.
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를 수정하라는 요청이 없으므로 큐를 잠그고 큐를 직접 순회하고 그 안에 있는 각 채널에 알릴 필요가 없습니다.
드디어
이렇게 하면 타임아웃이 포함된 조건 변수 효과가 구현됩니다. 실제로 제 장면은 방송 조건 예시를 참조하여 원하는 효과를 구현하고 학습할 수 있습니다. 그것.
위 내용은 시간 제한이 있는 세마포어의 golang 시뮬레이션 구현을 설명하는 예의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!