>  기사  >  백엔드 개발  >  시간 제한이 있는 세마포어의 golang 시뮬레이션 구현을 설명하는 예

시간 제한이 있는 세마포어의 golang 시뮬레이션 구현을 설명하는 예

巴扎黑
巴扎黑원래의
2017-09-07 10:09:552005검색

이 글은 주로 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.