프로 세 스 간 공유 메모 리 를 해결 합 니 다. 프로 세 스 가 이상 하 게 종료 되 어 잠 금 문제 가 발생 했 습 니 다.
문제점 을 발견 하 다
이 블 로그 가 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 자 물 쇠 는 더욱 좋 은 성능 을 가지 고 있 습 니 다.사용자 상태 와 커 널 상태 가 혼합 되 어 사용 되 는 동기 화 체제 입 니 다. 잠 금 경쟁 이 없 을 때 사용자 상태 에서 되 돌아 갈 수 있 고 시스템 호출 이 필요 하지 않 습 니 다.
물론 모든 자 물 쇠 는 비용 이 있 습 니 다. 가능 한 한 사용 하지 않 아 도 됩 니 다. 더 블 버 퍼 를 사용 하여 링크 를 풀 고 인용 수 를 사용 하면 어느 정도 자 물 쇠 를 대체 할 수 있 습 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
java 자물쇠의 성능 향상 방법사실상 경쟁적이지 않은 상황에서 대부분의 응용 프로그램에서 JVM은 동기화에 최적화되었다.비경쟁 자물쇠는 실행 과정에서 어떠한 추가 비용도 가져오지 않는다.따라서 성능 문제로 자물쇠를 원망해서는 안 되고, 자물쇠의 ...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.