스 레 드 동기 화 이기 (1) - 재 귀 자물쇠 와 비 재 귀 자물쇠
가장 흔히 볼 수 있 는 프로 세 스 / 스 레 드 의 동기 화 방법 은 상호 배척 잠 금 (또는 상호 배척 량 Mutex), 읽 기 쓰기 잠 금 (rdlock), 조건 변수 (cond), 신 호 량 (Semophore) 등 이 있 습 니 다. Windows 시스템 에 서 는 임계 구역 (Critical Section) 과 이벤트 대상 (Event) 도 자주 사용 하 는 동기 화 방법 입 니 다.
쉽게 말 하면 상호 배척 자 물 쇠 는 하나의 임계 구역 을 보호 합 니 다. 이 임계 구역 에 서 는 한 번 에 최대 한 라인 만 들 어 갈 수 있 습 니 다. 같은 임계 구역 에서 여러 프로 세 스 가 활동 하면 경쟁 조건 (race condition) 에 오류 가 발생 할 수 있 습 니 다.
읽 기와 쓰기 자 물 쇠 는 넓 은 의미 의 논리 적 으로 도 공유 판 의 상호 배척 자물쇠 라 고 볼 수 있다. 한 임계 구역 에 대해 대부분 읽 기 작업 이 고 소량의 쓰기 작업 만 한다 면 읽 기와 쓰기 자 물 쇠 는 어느 정도 에 스 레 드 상호 배척 의 대 가 를 낮 출 수 있다.
조건 변 수 는 스 레 드 가 경쟁 없 이 어떤 조건 이 발생 하 기 를 기다 릴 수 있 도록 합 니 다. 이 조건 이 발생 하지 않 았 을 때 스 레 드 는 휴면 상태 에 있 습 니 다. 다른 스 레 드 알림 조건 이 발생 했 을 때 스 레 드 가 깨 어 나 계속 아래로 실 행 됩 니 다. 조건 변 수 는 바 텀 동기 화 원 어 를 비교 하여 직접 사용 하 는 경우 가 많 지 않 습 니 다. 고 층 간 을 실현 하 는 데 사 용 됩 니 다.조건 변 수 를 사용 하 는 대표 적 인 예 는 스 레 드 풀 (Thread Pool) 입 니 다.
운영 체제 의 프로 세 스 동기 화 원 리 를 배 울 때 가장 많이 말 하 는 것 은 신 호 량 입 니 다. 신 호 량 의 PV 조작 을 정성 들 여 설계 하면 복잡 한 프로 세 스 동기 화 상황 (예 를 들 어 전형 적 인 철학 자 식사 문제 와 이발소 문제) 을 실현 할 수 있 습 니 다.현실 적 인 프로 그래 밍 에 서 는 신 호 량 을 사용 하 는 사람 이 극히 적다. 신 호 량 으로 해결 할 수 있 는 문 제 는 신 호 량 대신 더 뚜렷 하고 간결 한 디자인 수단 을 사용 하 는 것 같다.
이 시리즈 의 목적 은 이러한 동기 화 방법 을 어떻게 사용 해 야 하 는 지 설명 하기 위 한 것 이 아 닙 니 다., 전략 자물쇠 (Strategized Locking), 읽 기와 쓰기 자물쇠 와 조건 변수, 이중 검 측 자물쇠 (DCL), 자물쇠 와 무관 한 데이터 구조 (Locking free), 자전 자물쇠 등 내용, 벽돌 을 던 져 옥 을 끌 어 올 릴 수 있 기 를 바 랍 니 다.
그러면 우 리 는 먼저 재 귀 자물쇠 와 비 재 귀 자물쇠 에서 열 어 봅 시다.)
1
1.1
모든 스 레 드 동기 화 방법 에 서 는 상호 배척 자물쇠 (mutex) 의 출전 률 이 다른 방법 보다 훨씬 높 을 것 같 습 니 다. 상호 배척 자물쇠 의 이해 와 기본 적 인 사용 방법 은 모두 쉬 우 므 로 더 이상 소개 하지 않 겠 습 니 다.
Mutex 는 재 귀적 잠 금 (recursive mutex) 과 비 재 귀적 잠 금 (non - recursive mutex) 으로 나 눌 수 있 습 니 다. 재 귀적 잠 금 은 재 귀적 잠 금 (reentrant mutex) 이 라 고도 할 수 있 으 며, 재 귀적 잠 금 이 아 닌 재 귀적 잠 금 (non - reentrant mutex) 이 라 고도 할 수 있 습 니 다.
두 사람의 유일한 차이 점 은 같은 스 레 드 가 같은 재 귀 자 물 쇠 를 여러 번 가 져 올 수 있 고 자물쇠 가 생기 지 않 는 다 는 것 이다. 한 스 레 드 가 같은 재 귀 자 물 쇠 를 여러 번 가 져 오 면 자물쇠 가 생 긴 다 는 것 이다.
Windows 의 Mutex 와 Critical Section 은 재 귀적 으로 사용 할 수 있 습 니 다. Linux 의 pthread mutex t 잠 금 은 기본적으로 재 귀적 이지 않 습 니 다. PTHREAD MUTEX RECURSIVE 속성 을 표시 하고 pthread mutex t 를 재 귀적 잠 금 으로 설정 할 수 있 습 니 다.
상호 반 론 량 을 어떻게 사용 하 는 지 를 소개 하 는 글 과 책 에서 이 두 가지 개념 은 무시 되 거나 얼렁뚱땅 넘 어가 많은 사람들 이 이 개념 을 전혀 모 르 게 만든다. 그러나 이 두 가지 자 물 쇠 를 잘못 사용 하면 프로그램의 잠 금 을 초래 할 수 있다. 아래 절 차 를 보 세 요.
MutexLock mutex;
void foo()
{
mutex.lock();
// do something
mutex.unlock();
}
void bar()
{
mutex.lock();
// do something
foo();
mutex.unlock();
}
foo 함수 와 bar 함 수 는 같은 자 물 쇠 를 가 져 왔 고 bar 함 수 는 foo 함 수 를 호출 합 니 다. MutexLock 자물쇠 가 비 재 귀적 자물쇠 라면 이 프로그램 은 즉시 자 물 쇠 를 잠 글 것 입 니 다. 따라서 프로그램 에 자 물 쇠 를 추가 할 때 특히 조심해 야 합 니 다. 그렇지 않 으 면 이러한 호출 관계 로 인해 자 물 쇠 를 잠 글 수 있 습 니 다.
요행 심리 가 존재 하지 마 세 요. 이런 경 우 는 드물다 고 생각 합 니 다. 코드 가 어느 정도 복잡 하고 여러 사람 에 의 해 유지 되 며 호출 관계 가 복잡 할 때 프로그램 에서 이런 실 수 를 하기 쉽 습 니 다. 다행히도 이런 원인 으로 인 한 자물쇠 가 쉽게 제거 되 었 습 니 다.
그러나 이 는 재 귀적 잠 금 대신 재 귀적 잠 금 을 사용 해 야 한 다 는 뜻 은 아 닙 니 다. 재 귀적 잠 금 은 사용 하기 도 쉽 지만 코드 문 제 를 숨 기 는 경우 가 많 습 니 다. 예 를 들 어 호출 함수 와 호출 함수 가 자 물 쇠 를 가 져 온 줄 알 고 같은 대상 을 수정 하고 있 을 때 문제 가 발생 하기 쉽 습 니 다. 따라서 재 귀적 잠 금 을 사용 할 수 있 는 상황 에서 재 귀적 잠 금 을 사용 해 야 합 니 다.잠 금 은 상대 적 으로 디 버 깅 을 통 해 쉽게 발견 된다. 프로그램 설계 에 문제 가 있 으 면 빨리 노출 될 수록 좋다.
1.2
위 와 같은 상황 으로 인 한 잠 금 을 피하 기 위해 AUPE v2 라 는 책 은 12 장 에서 디자인 방법 을 제시 했다. 즉, 하나의 함수 가 잠 금 을 추가 한 상태 에서 사용 할 수도 있 고 잠 금 을 추가 하지 않 은 상태 에서 사용 할 수도 있다. 이 함 수 를 두 가지 버 전 으로 나 누 는 경우 가 많다. 잠 금 버 전과 잠 금 을 추가 하지 않 은 버 전 (nolock 접미사 추가).
예 를 들 어 foo () 함 수 를 두 함수 로 분해 합 니 다.
//
void foo_nolock()
{
// do something
}
//
void fun()
{
mutex.lock();
foo_nolock();
mutex.unlock();
}
인터페이스의 미래 확장 성 을 위해 bar () 함 수 를 같은 방법 으로 bar withou lock () 함수 와 bar () 함수 로 뜯 을 수 있 습 니 다.
Douglas C. Schmidt (ACE 프레임 워 크 의 주요 작성 자) 의 'Strategized Locking, Thread - safe Interface, and Scoped Locking' 논문 에서 C + + 기반 스 레 드 보안 인터페이스 모드 (Thread - safe interface pattern) 를 제시 했다.AUPE 의 방법 과 는 다른 점 이 있 습 니 다. 즉, 인 터 페 이 스 를 설계 할 때 각 함수 도 두 개의 함수 로 분해 되 며, 잠 금 을 사용 하지 않 은 함 수 는 private 또는 proctected 형식 이 며, 잠 금 을 사용 하 는 함 수 는 Public 형식 입 니 다. 인 터 페 이 스 는 다음 과 같 습 니 다.
class T
{
public:
foo(); //
bar(); //
private:
foo_nolock();
bar_nolock();
}
대외 인터페이스의 Public 함 수 는 잠 금 이 없 는 개인 변수 함수 만 호출 할 수 있 을 뿐 서로 호출 할 수 없습니다. 함수 의 구체 적 인 실현 에 있어 서 이 두 가지 방법 은 기본적으로 같 습 니 다.
위 에서 말 한 두 가지 방법 은 일반적인 상황 에 서 는 문제 가 없 으 며, 잠 금 을 효과적으로 피 할 수 있 습 니 다. 그러나 일부 복잡 한 반전 상황 에 서 는 재 귀 잠 금 을 사용 해 야 합 니 다. 예 를 들 어 foo 함수 가 외부 라 이브 러 리 의 함 수 를 호출 했 고, 외부 라 이브 러 리 의 함 수 는 bar () 함 수 를 되 돌 렸 습 니 다. 이 때 는 재 귀 잠 금 을 사용 해 야 합 니 다. 그렇지 않 으 면 잠 금 이 사라 집 니 다. AUPE 한 책 은 12 장 에서 재 귀 자 물 쇠 를 사용 해 야 하 는 프로그램 예 를 들 었 다.
1.3
읽 기와 쓰기 자물쇠 (예 를 들 어 Linux 의 pthread rwlock t) 는 상호 배척 자물쇠 보다 높 은 수준의 동시 방문 을 제공 합 니 다. 읽 기와 쓰기 자물쇠 의 실현 은 상호 배척 자물쇠 보다 복잡 하기 때문에 비용 도 상호 배척 자물쇠 보다 많 습 니 다. 제 Linux 기계 에서 실험 한 결과 단순 한 자 물 쇠 를 쓰 는 시간 비용 차 이 는 상호 배척 자물쇠 보다 10 배 정도 많 지 않 습 니 다.
시스템 이 읽 기와 쓰기 자 물 쇠 를 지원 하지 않 을 때, 때로는 스스로 실현 해 야 한다. 일반적으로 조건 변수 에 읽 기와 쓰기 카운터 로 이 루어 진다. 때로는 실제 상황 에 따라 독자 우선 또는 쓰기 자 우선 의 읽 기와 쓰기 자 물 쇠 를 실현 할 수 있다.
읽 기와 쓰기 자물쇠 의 장점 은 읽 기 동작 이 빈번 하고 쓰기 동작 이 적은 경우 에 나타난다. 읽 기 동작 보다 쓰기 동작 이 많 고 쓰기 작업 시간 이 짧 으 면 프로그램의 대부분 비용 이 읽 기와 쓰기 자물쇠 에 쓰 인 다. 이때 오히려 상호 배척 자물쇠 로 효율 이 높다.
많은 학생 들 이 읽 기와 쓰기 자물쇠 의 기본 적 인 사용 방법 을 배 운 후에 다음 과 같은 프로그램 (Linux 에서 실현) 을 쓴 적 이 있다 고 믿 습 니 다.
#include <pthread.h>
int main()
{
pthread_rwlock_t rwl;
pthread_rwlock_rdlock(&rwl);
pthread_rwlock_wrlock(&rwl);
pthread_rwlock_unlock(&rwl);
pthread_rwlock_unlock(&rwl);
return -1;
}
/* 2*/
#include <pthread.h>
int main()
{
pthread_rwlock_t rwl;
pthread_rwlock_wrlock(&rwl);
pthread_rwlock_rdlock(&rwl);
pthread_rwlock_unlock(&rwl);
pthread_rwlock_unlock(&rwl);
return -1;
}
프로그램 1 은 읽 기 자 물 쇠 를 먼저 넣 고 자 물 쇠 를 추가 하 는 것 이 이치 상 막 혀 야 하지만 프로그램 은 순조롭게 실 행 될 수 있 고 프로그램 2 는 막 혔 다 는 것 을 알 게 될 것 이다.
한 걸음 더 가 까 워 지면 다음 프로그램 3 과 프로그램 4 를 실행 하면 무슨 일이 일어 날 지 말 할 수 있 습 니까?
/* 3*/
#include <pthread.h>
int main()
{
pthread_rwlock_t rwl;
pthread_rwlock_rdlock(&rwl);
pthread_rwlock_rdlock(&rwl);
pthread_rwlock_unlock(&rwl);
pthread_rwlock_unlock(&rwl);
return -1;
}
/* 4*/
#include <pthread.h>
int main()
{
pthread_rwlock_t rwl;
pthread_rwlock_wrlock(&rwl);
pthread_rwlock_wrlock(&rwl);
pthread_rwlock_unlock(&rwl);
pthread_rwlock_unlock(&rwl);
return -1;
}
POSIX 표준 에 서 는 하나의 스 레 드 가 먼저 자 물 쇠 를 얻 고 읽 기 자 물 쇠 를 얻 으 면 결 과 는 예측 할 수 없습니다. 이것 이 바로 프로그램 1 이 실행 되 는 이유 입 니 다. 읽 기 자 물 쇠 는 재 귀 자물쇠 (재 입 가능) 이 고, 자 물 쇠 는 재 귀 자물쇠 (즉 재 입 불가) 입 니 다. 따라서 프로그램 3 은 잠 금 이 되 지 않 고 프로그램 4 는 계속 막 힙 니 다.
읽 기와 쓰기 자 물 쇠 를 재 귀적 으로 사용 할 수 있 는 지 여 부 는 플랫폼 에 따라 다 를 수 있 으 므 로 헷 갈 리 지 않도록 같은 스 레 드 에서 읽 기와 쓰기 자 물 쇠 를 혼용 하 는 것 을 피 하 는 것 을 권장 합 니 다.
시스템 에서 재 귀 자 물 쇠 를 지원 하지 않 고 사용 해 야 할 때 는 재 귀 자 물 쇠 를 스스로 만들어 야 합 니 다. 일반적으로 재 귀 자 물 쇠 는 재 귀 상호 배척 자물쇠 와 인용 계수 기 를 사용 하여 이 루어 집 니 다. 쉽게 말 하면 자 물 쇠 를 추가 하기 전에 자 물 쇠 를 추가 한 스 레 드 와 현재 자 물 쇠 를 추가 한 스 레 드 가 같은 스 레 드 인지 아 닌 지 를 판단 합 니 다. 같은 스 레 드 라면 계수 기 를 참조 하여 1 만 추가 합 니 다.이 경우 인용 계수 기 를 1 로 설정 하면 현재 스 레 드 번 호 를 기록 하고 자 물 쇠 를 추가 합 니 다. 하나의 예 는 여 기 를 볼 수 있 습 니 다. 재 귀 자 물 쇠 를 공용 라 이브 러 리 로 사용 하려 면 더 많은 이상 상황 과 오류 처 리 를 고려 하여 코드 를 더욱 튼튼 하 게 해 야 합 니 다.
다음 절 에 저 는 평소에 흔히 볼 수 있 는 잠 금 수단 인 지역 잠 금 (Scoped Locking) 기술 을 소개 하 겠 습 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Exception in thread main java.lang. NoClassDefFoundError 오류 해결 방법즉,/home/hadoop/jarfile) 시스템은 Hello World 패키지 아래의class라는 클래스 파일을 실행하고 있다고 오인하여 시스템의 CLASSPATH 아래 (일반적으로 현재 디렉터리를 포함) Hell...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.