2008-10-03 3 views
8

handleRequest(string key)에 대한 함수 호출의 형태로 요청을 처리하는 다중 스레드 C++ 프로그램이 있다고 가정 해 보겠습니다. handleRequest에 대한 각 호출은 별도의 스레드에서 발생하며 임의로 많은 수의 가능한 값이 key에 있습니다.C++에서 임의의 문자열을 잠금으로 사용하려면 어떻게해야합니까?

나는 다음과 같은 동작을합니다 : 그들은 key에 대해 동일한 값을 가질 때 handleRequest(key)

  • 동시 호출을 직렬화합니다.
  • 전역 직렬화가 최소화됩니다.

handleRequest의 본문은 다음과 같습니다

void handleRequest(string key) { 
    KeyLock lock(key); 
    // Handle the request. 
} 

질문 :이 어떻게 필요한 동작을 취득 KeyLock을 구현하는 것이?

KeyLock::KeyLock(string key) { 
    global_lock->Lock(); 
    internal_lock_ = global_key_map[key]; 
    if (internal_lock_ == NULL) { 
     internal_lock_ = new Lock(); 
     global_key_map[key] = internal_lock_; 
    } 
    global_lock->Unlock(); 
    internal_lock_->Lock(); 
} 

KeyLock::~KeyLock() { 
    internal_lock_->Unlock(); 
    // Remove internal_lock_ from global_key_map iff no other threads are waiting for it. 
} 

을 ...하지만 글로벌 처음에 잠금 및 각 요청의 끝, 및 각 요청에 대해 별도의 Lock 객체의 생성이 필요합니다

본래의 구현은 다음과 같이 시작 수 있습니다. handleRequest에 대한 호출간에 경합이 높으면 문제가되지 않을 수 있지만 경합이 낮 으면 많은 오버 헤드가 발생할 수 있습니다.

답변

12

질문에있는 것과 비슷한 것을 할 수 있지만 단일 global_key_map 대신 여러 개의 (아마도 배열 또는 벡터) - 사용되는 문자열은 문자열의 간단한 해시 함수에 의해 결정됩니다.

그런 식으로 하나의 전역 잠금 대신 여러 독립적 인 잠금을 분산시킬 수 있습니다.

이것은 메모리 할당 자에서 자주 사용되는 패턴입니다 (패턴에 이름이 있는지는 잘 모름). 요청이 들어 오면 어떤 것이 할당 될 풀 (일반적으로 요청의 크기이지만 다른 매개 변수도 고려할 수 있음)을 결정한 다음 해당 풀만 잠글 필요가 있습니다. 할당 요청이 다른 풀을 사용할 다른 스레드에서 들어오는 경우 잠금 경합이 없습니다.

2
그것은 플랫폼에 따라 달라집니다

하지만 시도하려는 두 가지 기술은 다음과 같습니다

  • 사용 키 뮤텍스/동기화 개체, 개체 이름 =
  • 을 사용하여 파일 시스템 기반의 잠금 이름 공유 할 수없는 키 이름을 가진 임시 파일을 만들려고합니다. 이미 존재하는 경우 (= 이미 잠금)이 실패하고 은 OS의 세부 사항에 따라 달라집니다

두 기술을 다시 시도 폴링해야합니다. 실험을하고 어떤 작품을 볼 수 있습니다. .

+0

일반적으로 이름이 바뀐 Mutexes 만 만들 수 있습니다. 리눅스에서 적어도 얼마나 많은 것을 바꿀 수는 있지만,이 방법을 사용하여 오래된 Mutexes를 가비지 수집하는 것에주의해야합니다. –

2

아마도 std::map<std::string, MutexType>이 원하는 것일 수 있습니다. 여기서 MutexType은 원하는 뮤텍스 유형입니다.다른 스레드가 동시에 삽입되지 않도록 다른 뮤텍스의 맵에 대한 액세스를 래핑해야합니다 (뮤텍스가 잠긴 후 다시 검사하여 다른 스레드가 해당 스레드를 추가하지 않았는지 확인해야합니다). 뮤텍스를 기다리는 동안 키!).

같은 원리는 임계 섹션과 같은 다른 동기화 방법에도 적용될 수 있습니다.

+1

이것은 OP가 원하지 않는 것과 같은 구현입니다. –

2

올리기 세분화 및 전체 잠금이 여러 유체 잠금을 갖는 대신 키 범위에 적용 잠금 장치의 하나의 고정 된 배열을 매핑 대신 마이크 B의 대답에 변화가

키 범위 단일 키의.

간단한 예제 : 시작할 때 256 개의 잠금 배열을 만든 다음 key의 첫 번째 바이트를 사용하여 획득 할 잠금 색인을 결정합니다 (즉, 'k'로 시작하는 모든 키는 locks[107]으로 보호됩니다).

최적의 처리량을 유지하려면 키 배포 및 경합 비율을 분석해야합니다. 이 접근법의 이점은 제로 동적 할당과 단순 정리입니다. 또한 2 단계 잠금을 피할 수 있습니다. 키 분배가 시간이 지남에 따라 왜곡 될 경우 단점은 잠재적 인 경합 피크입니다.

0

그것에 대해 생각하면, 또 다른 방법은 다음과 같이 갈 수 있습니다 handleRequest에서

  • 은, 실제 작업을 수행하는 Callback을 만들 수 있습니다.
  • 뮤텍스로 보호되는 multimap<string, Callback*> global_key_map을 만듭니다.
  • 스레드에서 이미 처리중인 key이 있으면 Callback*global_key_map에 추가하고 반환합니다.
  • 그렇지 않으면 콜백을 즉시 호출 한 다음 동일한 키에 표시된 콜백을 호출합니다. 이 같은

구현 뭔가 : 이것은 그렇지 않으면 키 잠금을 기다리고있을 것이다 스레드를 확보하는 장점을 가지고 있지만, 간격은 거의 순진 솔루션과 동일하다고에서

LockAndCall(string key, Callback* callback) { 
    global_lock.Lock(); 
    if (global_key_map.contains(key)) { 
     iterator iter = global_key_map.insert(key, callback); 
     while (true) { 
      global_lock.Unlock(); 
      iter->second->Call(); 
      global_lock.Lock(); 
      global_key_map.erase(iter); 
      iter = global_key_map.find(key); 
      if (iter == global_key_map.end()) { 
       global_lock.Unlock(); 
       return; 
      } 
     } 
    } else { 
     global_key_map.insert(key, callback); 
     global_lock.Unlock(); 
    } 
} 

I 질문에 게시 됨.

Mike B와 Constantin이 제시 한 답변과 결합 할 수 있습니다.

0
 /** 
     * StringLock class for string based locking mechanism 
     * e.g. usage 
     *  StringLock strLock; 
     *  strLock.Lock("row1"); 
     *  strLock.UnLock("row1"); 
     */ 
     class StringLock { 
     public: 
      /** 
      * Constructor 
      * Initializes the mutexes 
      */ 
      StringLock() { 
       pthread_mutex_init(&mtxGlobal, NULL); 
      } 
      /** 
      * Lock Function 
      * The thread will return immediately if the string is not locked 
      * The thread will wait if the string is locked until it gets a turn 
      * @param string the string to lock 
      */ 
      void Lock(string lockString) { 
       pthread_mutex_lock(&mtxGlobal); 
       TListIds *listId = NULL; 
       TWaiter *wtr = new TWaiter; 
       wtr->evPtr = NULL; 
       wtr->threadId = pthread_self(); 
       if (lockMap.find(lockString) == lockMap.end()) { 
        listId = new TListIds(); 
        listId->insert(listId->end(), wtr); 
        lockMap[lockString] = listId; 
        pthread_mutex_unlock(&mtxGlobal); 
       } else { 
        wtr->evPtr = new Event(false); 
        listId = lockMap[lockString]; 
        listId->insert(listId->end(), wtr); 
        pthread_mutex_unlock(&mtxGlobal); 
        wtr->evPtr->Wait(); 
       } 
      } 
      /** 
      * UnLock Function 
      * @param string the string to unlock 
      */ 
      void UnLock(string lockString) { 
       pthread_mutex_lock(&mtxGlobal); 
       TListIds *listID = NULL; 
       if (lockMap.find(lockString) != lockMap.end()) { 
        lockMap[lockString]->pop_front(); 
        listID = lockMap[lockString]; 
        if (!(listID->empty())) { 
         TWaiter *wtr = listID->front(); 
         Event *thdEvent = wtr->evPtr; 
         thdEvent->Signal(); 
        } else { 
         lockMap.erase(lockString); 
         delete listID; 
        } 
       } 
       pthread_mutex_unlock(&mtxGlobal); 
      } 
     protected: 
      struct TWaiter { 
       Event *evPtr; 
       long threadId; 
      }; 
      StringLock(StringLock &); 
      void operator=(StringLock&); 
      typedef list TListIds; 
      typedef map TMapLockHolders; 
      typedef map TMapLockWaiters; 
     private: 
      pthread_mutex_t mtxGlobal; 
      TMapLockWaiters lockMap; 
     }; 
+0

이것은 OP가 원하지 않는 "글로벌 잠금"구현과 동일합니다. 게다가, 문체와 코드 재사용 성 노트에서 : 당신은 표준 pthreads *와 *이 정의되지 않은'Event' (기본적으로 pthreads sem_t와 유사합니다)를 사용하고 있습니다. – Quuxplusone

관련 문제