프로 세 스 간 공유 메모 리 를 해결 합 니 다. 프로 세 스 가 이상 하 게 종료 되 어 잠 금 문제 가 발생 했 습 니 다.

출처: 클릭 하여 링크 열기
문제점 을 발견 하 다
이 블 로그 가 Nginx 와 Fpm - php 등 내부 다 중 프로 세 스 간 공유 데이터 문 제 를 해결 한 데 이 어 프로 세 스 간 공유 메모리 에 또 새로운 문제 가 생 겼 습 니 다.
어제 저녁 에 QP 학생 이 접속 한 후에 아침 에 시간 초과 보고 서 를 보 니 전단 기계 한 대가 QP 를 방문 하 는 시간 이 초과 되 었 고 다른 전단 기계 보다 몇 개의 수량 급 이 높 았 으 며 전단 의 기 계 는 모두 같은 구조 이다.
설마 이 기계 시스템 이 정상 이 아 닌 건 아니 겠 지?시스템 상 태 를 살 펴 봐 도 아무런 이상 이 없 었 습 니 다. 시간 초과 로 그 를 통 계 했 는데 시간 초과 가 아침 에 QP 서비스 가 재 개 되 는 과정 에서 발생 한 것 을 발 견 했 습 니 다. 정상 적 인 상황 에서 서비스 가 재 개 될 때 ClusterMap 은 데이터 의 정상 적 인 분 배 를 보장 합 니 다.
혹시 클 러 스 터 맵 에 문제 가 있 나 요?ClusterMap 서버 에 가서 보 니 모든 것 이 정상 입 니 다.
혹시 구독 자 클 라 이언 트 에 문제 가 있 나 요?정상 적 인 기계 한 대 와 문제 가 있 는 이 기 계 를 비교 해 보 았 지만 로 그 를 봐 도 문제 가 발견 되 지 않 았 습 니 다. 조회 도 구 를 사용 하여 이 두 기계 구독 자가 대리 적 으로 쓴 공유 메모 리 를 검사 한 결과 도구 가 공유 메모리 를 읽 고 돌아 오 는 결과 가 일치 하지 않 는 것 을 발 견 했 습 니 다. 이것 은 더욱 이상 합 니 다. 모두 같은 구독 자 입 니 다. 한 기계 에 문제 가 있 으 면 한 대 는 문제 가 없습니다.
서버 에서 보 낸 메시지 가 일치 하지 않 습 니까?서버 에 가서 구독 자의 기계 목록 을 모두 꺼 냈 는데 문제 가 있 는 기계 가 구독 자 목록 에 없 는 것 을 발 견 했 습 니 다. 이 기 계 는 구독 하지 않 았 다 는 것 을 설명 합 니 다. 뭔 가 단서 가 있 는 것 같 습 니 다. 제 가 구독 한 QP 기계 검증 을 내 려 왔 는데 공유 내부 데이터 가 업데이트 되 지 않 았 다 는 것 을 발 견 했 습 니 다. pstack 이 프로 세 스 를 해 보 니 내부 의 업데이트 스 레 드 가 계속 잠 겨 있 는 것 을 발 견 했 습 니 다.공유 메모리 데 이 터 를 업데이트 할 수 없습니다. gdb 가 따라 들 어간 후,lock.data.nr_readers 는 1 이 었 습 니 다. 읽 기 프로 세 스 가 자 물 쇠 를 차지 하여 쓰기 프로 세 스 가 들 어 갈 수 없 음 을 설명 합 니 다. 모든 fpm - phop 의 읽 기 프로 세 스 가 자 물 쇠 를 차지 하지 않 은 것 을 발 견 했 습 니 다. 이 는 읽 기 프로 세 스 가 자 물 쇠 를 얻 은 후에 풀 지 못 하고 끊 었 다 는 것 을 의미 합 니 다.
테스트
현재 문 제 는 읽 기 자 물 쇠 를 얻 은 후 프로 세 스 가 이상 하 게 종료 되 어 발생 한 것 으로 확인 되 었 습 니 다. 테스트 프로그램 을 써 서 이 문 제 를 재현 하 겠 습 니 다.
(! 2293)-> cat test/read_shared.cpp
#include

SharedUpdateData*   _sharedUpdateData = NULL;
cm_sub::CMMapFile*  _mmapFile = NULL;

int32_t initSharedMemRead(const std::string& mmap_file_path)
{
    _mmapFile = new (std::nothrow) cm_sub::CMMapFile();
    if (_mmapFile == NULL || !_mmapFile->open(mmap_file_path.c_str(), FILE_OPEN_WRITE) )
    {
        return -1;
    }
    _sharedUpdateData = (SharedUpdateData*)_mmapFile->offset2Addr(0);
    return 0;
}

int main(int argc, char** argv)
{
    if (initSharedMemRead(argv[1]) != 0) return -1;

    int cnt = 100;
    while (cnt > 0)
    {
        pthread_rwlock_rdlock( &(_sharedUpdateData->_lock));
        fprintf(stdout, "version = %ld, readers = %u
", _sharedUpdateData->_version, _sharedUpdateData->_lock.__data.__nr_readers); if (cnt == 190) { exit(0); } sleep(1); pthread_rwlock_unlock( &(_sharedUpdateData->_lock)); -- cnt; usleep(100*1000); } delete _mmapFile; }

(! 2293)-> cat test/write_shared.cpp
#include

SharedUpdateData*   _sharedUpdateData = NULL;
cm_sub::CMMapFile*  _mmapFile = NULL;

int32_t initSharedMemWrite(const char* mmap_file_path)
{
    _mmapFile = new (std::nothrow) cm_sub::CMMapFile();
    if ( _mmapFile == NULL || !_mmapFile->open(mmap_file_path, FILE_OPEN_WRITE, 1024) )
    {
        return -1;
    }
    _sharedUpdateData = (SharedUpdateData *)_mmapFile->offset2Addr(0);
    madvise(_sharedUpdateData, 1024, MADV_SEQUENTIAL);

    pthread_rwlockattr_t attr;
    memset(&attr, 0x0, sizeof(pthread_rwlockattr_t));
    if (pthread_rwlockattr_init(&attr) != 0 || pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED) != 0)
    {
        return -1;
    }
    pthread_rwlock_init( &(_sharedUpdateData->_lock), &attr);
    _sharedUpdateData->_updateTime = autil::TimeUtility::currentTime();
    _sharedUpdateData->_version = 0; 
    return 0;
}

int main()
{
    if (initSharedMemWrite("data.mmap") != 0) return -1;

    int cnt = 200;
    while (cnt > 0)
    {
        pthread_rwlock_wrlock( &(_sharedUpdateData->_lock));
        ++ _sharedUpdateData->_version;
        fprintf(stdout, "version = %ld, readers = %u
", _sharedUpdateData->_version, _sharedUpdateData->_lock.__data.__nr_readers); sleep(1); pthread_rwlock_unlock( &(_sharedUpdateData->_lock)); -- cnt; usleep(100*1000); } delete _mmapFile; }

읽 기 프로 세 스 든 쓰기 프로 세 스 든 자 물 쇠 를 가 져 오 면 풀 리 지 않 고 끊 는 문제 가 있 습 니 다.
어떻게 해결 합 니까?
문 제 는 이미 재현 되 었 습 니 다. 어떻게 좋 은 방법 으로 해결 할 것 인 가 를 생각 했 습 니 다. 인터넷 에서 한 번 찾 아 보 았 습 니 다. 읽 기와 쓰기 자물쇠 에 대해 좋 은 해결 방법 이 없고 논리 적 으로 만 스스로 해결 할 수 있 습 니 다. 생각 나 는 것 은 시간 초과 체 제 를 사용 하 는 것 입 니 다. 즉, 쓰기 프로 세 스 내부 에 시간 초과 시간 을 추가 하 는 것 입 니 다. 만약 에 프로 세 스 가 이 시간 에 도 자 물 쇠 를 얻 지 못 하면 자물쇠 라 고 생각 하고 읽 기 프로 세 스 의 수 를 1 로이것 은 폭력 적 인 해결 방법 이다. 설명 하지 않 겠 다. 만약 누가 나 에 게 좋 은 해결 방법 이 있다 면 지도 해 줘.
읽 기와 쓰기 자물쇠 의 코드 를 보 세 요. 읽 기와 쓰기 자 물 쇠 는 서로 배척 하 는 자물쇠 보다 읽 기와 쓰기 가 적은 장면 에 사용 하기에 더욱 적합 합 니 다. 만약 에 읽 기 프로 세 스 가 오래 걸 리 면 읽 기와 쓰기 자 물 쇠 를 사용 하기에 더욱 적합 합 니 다. 제 가 해 야 할 장면 은 읽 기와 쓰기 가 적 고 읽 기와 쓰기 시간 이 매우 짧 습 니 다.일시 적 으로 상호 배척 자물쇠 와 읽 기와 쓰기 자물쇠 의 성능 차이 가 크 지 않 을 것 이 라 고 생각 합 니 다. 사실은 읽 기와 쓰기 자물쇠 내부 에 도 상호 배척 자 물 쇠 를 사 용 했 습 니 다. 잠 금 의 시간 이 비교적 짧 고 상호 배척 구역 을 잠 그 고 들 어가 서 누군가가 쓰 고 있 는 지 확인 한 다음 에 풀 렸 습 니 다. 주의해 야 할 것 은 읽 기와 쓰기 자 물 쇠 는 기본적으로 쓰 는 것 이 우선 입 니 다. 즉, 쓰 고 있 거나 쓰기 대기 열 에 들 어가 서 쓰 려 고 할 때 입 니 다.읽 기 자 물 쇠 는 모두 추가 할 수 없 으 니 기 다 려 야 한다.
자, 그럼 상호 배척 자물쇠 가 우리 의 문 제 를 해결 할 수 있 는 지 봅 시다. 상호 배척 자물쇠 내부 에 Robust 자물쇠 라 는 속성 이 있 습 니 다.
자물쇠 설정: pthreadmutexattr_setrobust_np
        The robustness attribute defines the behavior when the owner
    of  a  mutex  dies.  The value of robustness could be either
    PTHREAD_MUTEX_ROBUST_NP or  PTHREAD_MUTEX_STALLED_NP,  which
    are  defined by the header <pthread.h>. The default value of
    the robustness attribute is PTHREAD_MUTEX_STALLED_NP.

        When the owner of a mutex with the  PTHREAD_MUTEX_STALLED_NP
    robustness    attribute    dies,   all   future   calls   to
    pthread_mutex_lock(3C) for this mutex will be  blocked  from
    progress in an unspecified manner.

일치 하지 않 는 Robust 자물쇠 복구: pthreadmutex_consistent_np
        A consistent mutex becomes inconsistent and is  unlocked  if
    its  owner dies while holding it, or if the process contain-
    ing the owner of the mutex unmaps the memory containing  the
    mutex or performs one of the exec(2) functions. A subsequent
    owner  of  the   mutex   will   acquire   the   mutex   with
    pthread_mutex_lock(3C),  which  will  return  EOWNERDEAD  to
    indicate that the acquired mutex is inconsistent.

        The pthread_mutex_consistent_np() function should be  called
    while  holding  the  mutex  acquired  by  a previous call to
    pthread_mutex_lock() that returned EOWNERDEAD.

        Since the critical section protected by the mutex could have
    been  left  in  an inconsistent state by the dead owner, the
    caller should make the mutex consistent only if it  is  able
    to  make  the  critical  section protected by the mutex con-
    sistent.

쉽게 말 하면 EOWNERDEAD 를 발 견 했 을 때 pthreadmutex_consistent_np 함수 내부 에서 이 상호 배척 자물쇠 가 Robust 자물쇠 인지 아 닌 지 판단 합 니 다. 만약 에 그 가 Owner Die 를 했다 면 그 는 자물쇠 의 owner 를 자신의 프로 세 스 ID 로 설정 할 것 입 니 다. 그러면 이 자 물 쇠 는 다시 사용 할 수 있 습 니 다. 간단 하 죠?
잠 금 해제 가 해 결 될 수 있 지만 공유 메모리 로 프로 세 스 간 에 데 이 터 를 공유 할 때 주의해 야 할 것 은 데이터 의 정확성, 즉 완전 성 입 니 다. 프로 세 스 공유 가 스 레 드 와 다 릅 니 다. 한 프로 세 스 의 여러 스 레 드 라면 프로 세 스 가 이상 하 게 종료 되 고 다른 스 레 드 도 동시에 종료 되 며 프로 세 스 간 공 유 는 독립 적 입 니 다.만약 에 하나의 스 레 드 가 공유 데 이 터 를 쓰 는 과정 에서 이상 하 게 종료 되 어 기 록 된 데이터 가 완전 하지 않 으 면 읽 기 프로 세 스 가 읽 을 때 불완전한 데 이 터 를 읽 는 문제 가 발생 합 니 다. 사실은 데이터 의 완전 성 이 매우 잘 해결 되 고 공유 메모리 에 완성 표 시 를 추가 하면 됩 니 다. 공유 구역 을 잠 근 후에 데 이 터 를 쓰 고 작성 한 후에 완성 으로 표시 하면 됩 니 다.읽 기 프로 세 스 가 읽 을 때 완료 표 시 를 판단 합 니 다.
테스트 코드 참조:
(! 2295)-> cat test/read_shared_mutex.cpp
 #include 

 SharedUpdateData*   _sharedUpdateData = NULL;
 cm_sub::CMMapFile*  _mmapFile = NULL;

 int32_t initSharedMemRead(const std::string& mmap_file_path)
 {
    _mmapFile = new (std::nothrow) cm_sub::CMMapFile();
    if (_mmapFile == NULL || !_mmapFile->open(mmap_file_path.c_str(), FILE_OPEN_WRITE) )
    {
        return -1;
    }
    _sharedUpdateData = (SharedUpdateData*)_mmapFile->offset2Addr(0);
    return 0;
 }

 int main(int argc, char** argv)
 {
     if (argc != 2) return -1;
     if (initSharedMemRead(argv[1]) != 0) return -1;   

     int cnt = 10000;
     int ret = 0;
     while (cnt > 0)
     {
         ret = pthread_mutex_lock( &(_sharedUpdateData->_lock));
         if (ret == EOWNERDEAD)
         {
             fprintf(stdout, "%s: version = %ld, lock = %d, %u, %d
", strerror(ret), _sharedUpdateData->_version, _sharedUpdateData->_lock.__data.__lock, _sharedUpdateData->_lock.__data.__count, _sharedUpdateData->_lock.__data.__owner); ret = pthread_mutex_consistent_np( &(_sharedUpdateData->_lock)); if (ret != 0) { fprintf(stderr, "%s
", strerror(ret)); pthread_mutex_unlock( &(_sharedUpdateData->_lock)); continue; } } fprintf(stdout, "version = %ld, lock = %d, %u, %d
", _sharedUpdateData->_version, _sharedUpdateData->_lock.__data.__lock, _sharedUpdateData->_lock.__data.__count, _sharedUpdateData->_lock.__data.__owner); sleep(5); pthread_mutex_unlock( &(_sharedUpdateData->_lock)); usleep(500*1000); -- cnt; } fprintf(stdout, "go on
"); delete _mmapFile; }

(! 2295)-> cat test/write_shared_mutex.cpp
#include 

SharedUpdateData*   _sharedUpdateData = NULL;
cm_sub::CMMapFile*  _mmapFile = NULL;


int32_t initSharedMemWrite(const char* mmap_file_path)
{
    _mmapFile = new (std::nothrow) cm_sub::CMMapFile();
    if ( _mmapFile == NULL || !_mmapFile->open(mmap_file_path, FILE_OPEN_WRITE, 1024) )
    {
        return -1;
    }
    _sharedUpdateData = (SharedUpdateData *)_mmapFile->offset2Addr(0);
    madvise(_sharedUpdateData, 1024, MADV_SEQUENTIAL);

    pthread_mutexattr_t attr;
    memset(&attr, 0x0, sizeof(pthread_mutexattr_t));
    if (pthread_mutexattr_init(&attr) != 0 || pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED) != 0)
    {
        return -1;
    }
    if (pthread_mutexattr_setrobust_np(&attr, PTHREAD_MUTEX_ROBUST_NP) != 0)
    {
        return -1;
    }
    pthread_mutex_init( &(_sharedUpdateData->_lock), &attr);
    _sharedUpdateData->_version = 0;
    return 0;
}

int main()
{
    if (initSharedMemWrite("data.mmap") != 0) return -1;

    int cnt = 200;
    int ret = 0;
    while (cnt > 0)
    {
        ret = pthread_mutex_lock( &(_sharedUpdateData->_lock));
        if (ret == EOWNERDEAD)
        {
            fprintf(stdout, "%s: version = %ld, lock = %d, %u, %d
", strerror(ret), _sharedUpdateData->_version, _sharedUpdateData->_lock.__data.__lock, _sharedUpdateData->_lock.__data.__count, _sharedUpdateData->_lock.__data.__owner); ret = pthread_mutex_consistent_np( &(_sharedUpdateData->_lock)); if (ret != 0) { fprintf(stderr, "%s
", strerror(ret)); pthread_mutex_unlock( &(_sharedUpdateData->_lock)); continue; } } ++ _sharedUpdateData->_version; fprintf(stdout, "version = %ld, lock = %d, %u, %d
", _sharedUpdateData->_version, _sharedUpdateData->_lock.__data.__lock, _sharedUpdateData->_lock.__data.__count, _sharedUpdateData->_lock.__data.__owner); usleep(1000*1000); pthread_mutex_unlock( &(_sharedUpdateData->_lock)); -- cnt; usleep(500*1000); } delete _mmapFile; }

BTW: 우 리 는 모두 자 물 쇠 를 추가 하 는 것 이 비용 이 있다 는 것 을 알 고 있 습 니 다. 상호 배척 으로 인 한 대기 비용 뿐만 아니 라 자 물 쇠 를 추가 하 는 과정 도 모두 시스템 이 커 널 상태 로 호출 되 었 습 니 다. 이 과정 은 비용 도 많이 들 고 서로 배척 하 는 자물쇠 가 있 습 니 다. Futex 자물쇠 (Fast User Mutex) 라 고 합 니 다. 리 눅 스 는 2.5.7 버 전부터 Futex 를 지 니 고 빠 른 사용자 차원 의 상호 배척 자물쇠, Fetux 자 물 쇠 는 더욱 좋 은 성능 을 가지 고 있 습 니 다.사용자 상태 와 커 널 상태 가 혼합 되 어 사용 되 는 동기 화 체제 입 니 다. 잠 금 경쟁 이 없 을 때 사용자 상태 에서 되 돌아 갈 수 있 고 시스템 호출 이 필요 하지 않 습 니 다.
물론 모든 자 물 쇠 는 비용 이 있 습 니 다. 가능 한 한 사용 하지 않 아 도 됩 니 다. 더 블 버 퍼 를 사용 하여 링크 를 풀 고 인용 수 를 사용 하면 어느 정도 자 물 쇠 를 대체 할 수 있 습 니 다.

좋은 웹페이지 즐겨찾기