객체 풀 패턴
Object pool pattern객체 풀 패턴은 소프트웨어 창조 설계 패턴으로, 필요에 따라 할당하고 파괴하는 것이 아니라 사용할 준비가 된 초기화된 객체 집합("풀")을 사용한다.풀의 클라이언트는 풀에서 개체를 요청하고 반환된 개체에 대해 작업을 수행한다.클라이언트가 작업을 마치면 객체를 파괴하는 대신 풀로 반환한다. 이 작업은 수동으로 또는 자동으로 수행될 수 있다.
객체 풀은 주로 성능에 사용된다. 어떤 상황에서는 객체 풀이 성능을 상당히 향상시킨다.개체 풀은 풀에서 획득하여 풀로 반환되는 물체가 실제로 생성되거나 파괴되지 않으므로 개체 수명을 복잡하게 한다.
설명
인스턴스화하는 데 특히 비용이 많이 들고 각 개체는 단기간 동안만 필요한 수많은 개체와 함께 작업할 필요가 있을 때, 전체 애플리케이션의 성능에 부정적인 영향을 미칠 수 있다.이와 같은 경우 객체 풀 설계 패턴은 바람직한 것으로 간주될 수 있다.
객체 풀 설계 패턴은 재사용될 수 있는 객체 집합을 만든다.새로운 물체가 필요할 때는 풀에서 요청한다.이전에 준비한 물체를 사용할 수 있는 경우, 즉석 비용을 피해 즉시 반환한다.풀에 개체가 없으면 새 항목이 생성되어 반환된다.객체가 사용되어 더 이상 필요하지 않을 때는 풀로 반환되어 계산적으로 비용이 많이 드는 인스턴스화 과정을 반복하지 않고 미래에 다시 사용할 수 있게 된다.일단 객체를 사용하고 반환하면 기존 참조가 무효가 된다는 점에 유의해야 한다.
일부 개체 풀에서는 리소스가 제한되므로 최대 개체 수가 지정된다.이 번호에 도달하여 새 항목을 요청하면 예외가 발생하거나, 개체를 풀로 다시 놓을 때까지 스레드가 차단될 수 있다.
객체 풀 설계 패턴은 의 표준 클래스의 여러 위치에서 사용된다.NET Framework.한 가지 예는 입니다.SQL Server용 NET Framework 데이터 공급자.SQL Server 데이터베이스 연결이 느릴 수 있기 때문에 연결 풀이 유지된다.연결을 닫아도 SQL Server에 대한 링크는 포기되지 않는다.대신 새 연결을 요청할 때 이 연결을 검색할 수 있는 풀에 연결된다.이것은 연결 속도를 상당히 증가시킨다.
혜택들
객체 풀링은 클래스 인스턴스 초기화 비용이 높고 클래스의 인스턴스화 및 파괴 비율이 높은 상황에서 상당한 성능 향상을 제공할 수 있다. 이 경우 객체를 자주 재사용할 수 있으며, 재사용할 때마다 상당한 시간을 절약할 수 있다.객체 풀링에는 메모리 및 네트워크 소켓과 같은 다른 자원이 필요하므로, 한 번에 사용 중인 인스턴스 수가 적은 것이 바람직하지만, 이는 필요하지 않다.
풀링된 객체는 (특히 네트워크를 통해) 새로운 객체를 생성하는 데 가변적인 시간이 걸릴 수 있는 예측 가능한 시간에 얻는다.이러한 이점은 데이터베이스 연결, 소켓 연결, 스레드 및 글꼴이나 비트맵과 같은 큰 그래픽 개체와 같이 시간과 관련하여 비용이 많이 드는 개체에 대부분 적용된다.
다른 상황에서는 (외부 자원을 보유하지 않고 메모리만 점유하는) 단순 객체 풀링이 효율적이지 않을 수 있으며 성능을 저하시킬 수 있다.[1]단순 메모리 풀링의 경우 단편화를 줄여 메모리 할당 및 할당 해제 비용을 최소화하는 것이 유일한 목표인 만큼 슬래브 할당 메모리 관리 기법이 더 적합하다.
실행
객체 풀은 스마트 포인터를 통해 C++와 같은 언어로 자동 구현될 수 있다.스마트 포인터 생성자에서는 풀에서 개체를 요청할 수 있으며, 스마트 포인터 소멸자에서는 개체를 풀로 다시 해제할 수 있다.가비지 수집 언어에서 파괴자가 없는 경우(스택 언윙의 일부로 불릴 것을 보장함) 객체 풀은 공장에서 명시적으로 객체를 요청하고 폐기 방법(처리 패턴에서처럼)을 호출하여 객체를 반환함으로써 수동으로 구현해야 한다.결승 진출자를 사용하는 것은 좋은 생각이 아니다. 일반적으로 결승 진출자가 언제(또는 실행될 것인지)에 대한 보장이 없기 때문이다.대신, "노력해봐...마지막으로" 객체를 가져오고 해제하는 것이 예외 중립적인지 확인하기 위해 사용되어야 한다.
수동 개체 풀은 구현이 간단하지만 풀 개체의 수동 메모리 관리가 필요하기 때문에 사용하기 더 어렵다.
빈 풀 처리
오브젝트 풀은 풀에 예비 오브젝트가 없을 때 요청을 처리하기 위해 세 가지 전략 중 하나를 사용한다.
- 개체를 제공하지 못함(오류를 클라이언트에 반환함)
- 새 개체를 할당하여 풀의 크기를 늘리십시오.이 작업을 수행하는 풀에서는 일반적으로 높은 수위 표시(사용된 최대 물체 수)를 설정할 수 있다.
- 다중 스레드 환경에서 풀은 다른 스레드가 개체를 풀에 반환할 때까지 클라이언트를 차단할 수 있다.
함정스
풀에 반환되는 물체의 상태가 다음에 물체를 사용하기 위해 감각적인 상태로 재설정되도록 주의를 기울여야 한다. 그렇지 않으면 물체가 클라이언트에 의해 예기치 않은 상태에 있을 수 있으며 이로 인해 물체가 실패할 수 있다.클라이언트가 아니라 풀에서 개체를 재설정할 책임이 있다.위험할 정도로 퀴퀴한 상태의 물체로 가득 찬 물체풀을 물체 세스풀이라고 부르기도 하며, 안티패턴으로 간주하기도 한다.
퀴퀴한 상태가 항상 문제가 되는 것은 아니다; 그것은 물체가 예기치 않게 행동하게 할 때 위험해진다.예를 들어, 인증 세부사항을 나타내는 개체는 사용자가 인증되지 않았을 때(아마도 다른 사용자처럼) 인증되었다는 것을 나타내기 때문에, "성공적으로 인증된" 플래그가 재사용되기 전에 재설정되지 않은 경우 인증 세부사항을 나타내는 개체가 실패할 수 있다.그러나 마지막으로 사용한 인증 서버의 ID와 같이 디버깅에만 사용되는 값을 재설정하지 않는 것은 문제가 되지 않을 수 있다.
물체의 부적절한 리셋은 정보 누설을 야기할 수 있다.기밀 데이터가 포함된 개체(예: 사용자의 신용카드 번호)는 신규 고객에게 전달되기 전에 삭제해야 하며 그렇지 않으면 해당 데이터가 허가되지 않은 당사자에게 공개될 수 있다.
여러 개의 스레드에 의해 풀이 사용되는 경우, 병렬 스레드가 동일한 객체를 병렬로 재사용하려고 시도하지 않도록 하는 수단이 필요할 수 있다.풀링된 물체가 불변하거나 다른 방법으로 나사산이 안전한 경우에는 필요하지 않다.
비판
일부 간행물에서는 Java와 같은 특정 언어와 함께 객체 풀링을 사용하는 것을 권장하지 않으며, 특히 외부 리소스를 사용하지 않는 객체(예: 데이터베이스 연결)에 대해서는 특히 권장하지 않는다.반대론자들은 대개 현대 언어에서 쓰레기 수집가들과 함께 물체 할당이 비교적 빠르다고 말한다.new
고전적인 10가지 지침만 있으면 된다.new
-delete
풀링 설계에서 발견되는 쌍은 더 복잡한 작업을 하기 때문에 수백 개의 쌍을 요구한다.또한 대부분의 가비지 수집기는 "실시간" 객체 참조를 스캔하며, 이러한 객체가 컨텐츠에 사용하는 메모리는 스캔하지 않는다.참조가 없는 '죽은' 물체는 거의 비용 없이 얼마든지 폐기할 수 있다는 뜻이다.이와는 대조적으로, "실시간"을 많이 유지하지만 사용되지 않는 물체는 가비지 수집 기간을 늘린다.[1]
예
가다
다음 Go 코드는 채널을 통한 리소스 경합 문제를 피하기 위해 지정된 크기의 리소스 풀(동시 초기화)을 초기화하며, 빈 풀의 경우 클라이언트가 너무 오래 기다리지 않도록 시간 초과 처리를 설정한다.
// 패키지 풀 꾸러미 포켓볼을 치다 수입하다 ( "errors" "로그" "산술/랜드" "sync" "시간" ) 경시하다 getResMaxTime = 3 * 시간.둘째 시합을 하다 ( ErrPoolNotExce = 오류.새로 만들기("풀은 존재하지 않음") ErrGetResTimeout = 오류.새로 만들기("리소스 시간 초과") ) //리소스 타자를 치다 자원 구조상의 { resid 인트로 } //NewResource 느린 리소스 초기화 생성 시뮬레이션 // (예: TCP 연결, SSL 대칭 키 획득, 인증은 시간이 많이 소요됨) 펑크 뉴리소스(id 인트로) *자원 { 시간.잠(500 * 시간.밀리초) 돌아오다 &자원{resid: id} } //시뮬레이션 리소스는 시간 소모적이고 무작위 소비는 0~400ms인 경우 펑크 (r *자원) 하다(workId 인트로) { 시간.잠(시간.기간(랜드.intn(5)) * 100 * 시간.밀리초) 통나무를 하다.프린트프("리소스 사용 #%d 완료 작업 %d 마침\n", r.resid, workId) } //Go Channel 구현을 기반으로 리소스 경쟁 상태 문제 방지 타자를 치다 풀 찬을 치다 *자원 //지정된 크기의 리소스 풀 새로 만들기 // 리소스 초기화 시간을 절약하기 위해 리소스를 동시에 생성 펑크 새로 만들기(사이즈를 맞추다 인트로) 풀 { p := 만들다(풀, 사이즈를 맞추다) wg := 새로운(동기를 맞추다.WaitGroup) wg.추가하다(사이즈를 맞추다) 을 위해 i := 0; i < 사이즈를 맞추다; i++ { 가다 펑크(resid 인트로) { p <- 뉴리소스(resid) wg.끝() }(i) } wg.잠깐() 돌아오다 p } //채널 기반 GetResource, 리소스 경합 상태가 방지되고 리소스 획득 시간 초과가 빈 풀에 대해 설정됨 펑크 (p 풀) GetResource() (r *자원, 잘못을 저지르다 착오) { 선발하다 { 케이스 r := <-p: 돌아오다 r, 못을 박다 케이스 <-시간.후(getResMaxTime): 돌아오다 못을 박다, ErrGetResTimeout } } //GiveBackResource 리소스 풀로 리소스 반환 펑크 (p 풀) 기브백리소스(r *자원) 착오 { 만일 p == 못을 박다 { 돌아오다 ErrPoolNotExce } p <- r 돌아오다 못을 박다 } // 패키지 메인 꾸러미 본래의 수입하다 ( "github.com/tkstorm/go-design/creational/object-pool/pool" "로그" "sync" ) 펑크 본래의() { // 5개의 리소스로 구성된 풀을 초기화하십시오. // 1 또는 10으로 조정하여 차이를 확인할 수 있음 사이즈를 맞추다 := 5 p := 포켓볼을 치다.새로 만들기(사이즈를 맞추다) // ID 작업을 수행하기 위해 리소스 호출 doWork := 펑크(workId 인트로, wg *동기를 맞추다.WaitGroup) { 연기하다 wg.끝() // 리소스 풀에서 리소스 가져오기 재방송하다, 잘못을 저지르다 := p.GetResource() 만일 잘못을 저지르다 != 못을 박다 { 통나무를 하다.프린틀른(잘못을 저지르다) 돌아오다 } // 반환할 리소스 연기하다 p.기브백리소스(재방송하다) // 리소스를 사용하여 작업 처리 재방송하다.하다(workId) } // 자산 풀에서 리소스를 가져오기 위해 100개의 동시 프로세스 시뮬레이션 숫자 := 100 wg := 새로운(동기를 맞추다.WaitGroup) wg.추가하다(숫자) 을 위해 i := 0; i < 숫자; i++ { 가다 doWork(i, wg) } wg.잠깐() }
C#
에서NET Base Class Library에는 이 패턴을 구현하는 몇 가지 개체가 있다. System.Threading.ThreadPool
할당할 스레드 수가 미리 정의되도록 구성됨.스레드가 반환되면 다른 계산에 사용할 수 있다.따라서 실의 생성과 폐기 비용을 지불하지 않고 실을 사용할 수 있다.
다음은 C#를 사용하여 구현된 객체 풀 설계 패턴의 기본 코드를 보여준다.간결성을 위해 클래스 속성은 C# 3.0 자동 구현 속성 구문을 사용하여 선언된다.이것들은 이전 버전의 언어에 대한 완전한 속성 정의로 대체될 수 있다.여러 개의 풀이 필요한 경우는 드물기 때문에 풀은 정적 클래스로 표시된다.그러나 객체 풀에 인스턴스 클래스를 사용하는 것도 마찬가지로 허용된다.
네임스페이스 디자인패턴.객체 풀; // PollatedObject 클래스는 비용이 많이 들거나 인스턴스화 속도가 느린 유형이다. // 또는 가용성이 제한되어 있으므로 객체 풀에 보관해야 한다. 공중의 계급 풀링 오브젝트 { 사유의 날짜 시간 _createdAt = 날짜 시간.지금; 공중의 날짜 시간 작성 위치 { 얻다 { 돌아오다 _createdAt; } } 공중의 끈을 매다 온도 데이터 { 얻다; 세트; } } // 풀 클래스는 풀링된 개체에 대한 액세스를 제어한다.사용 가능한 개체 목록과 // 풀에서 획득하여 사용 중인 개체의 모음입니다.풀은 해제된 객체를 보장한다. // 다시 사용할 수 있는 적절한 상태로 되돌아간다. 공중의 정태의 계급 풀 { 사유의 정태의 리스트<풀링 오브젝트> _사용가능 = 새로운 리스트<풀링 오브젝트>(); 사유의 정태의 리스트<풀링 오브젝트> _inUse = 새로운 리스트<풀링 오브젝트>(); 공중의 정태의 풀링 오브젝트 GetObject() { 자물쇠를 채우다 (_사용가능) { 만일 (_사용가능.카운트 != 0) { 풀링 오브젝트 포를 뜨다 = _사용가능[0]; _inUse.추가하다(포를 뜨다); _사용가능.RemoveAt(0); 돌아오다 포를 뜨다; } 다른 { 풀링 오브젝트 포를 뜨다 = 새로운 풀링 오브젝트(); _inUse.추가하다(포를 뜨다); 돌아오다 포를 뜨다; } } } 공중의 정태의 공허하게 하다 릴리스 오브젝트(풀링 오브젝트 포를 뜨다) { 정리하다(포를 뜨다); 자물쇠를 채우다 (_사용가능) { _사용가능.추가하다(포를 뜨다); _inUse.제거하다(포를 뜨다); } } 사유의 정태의 공허하게 하다 정리하다(풀링 오브젝트 포를 뜨다) { 포를 뜨다.온도 데이터 = 무효의; } }
위의 코드에서 PollatedObject는 생성된 시간에 대한 속성과 클라이언트가 수정할 수 있는 속성을 가지고 있으며, PullatedObject가 풀로 다시 해제될 때 재설정된다.표시된 것은 물체를 풀에서 다시 요청할 수 있기 전에 물체가 유효한 상태인지 확인하는 정리 과정이다.
자바
Java는 다음을 통해 스레드 풀링 지원java.util.concurrent.ExecutorService
기타 관련 클래스.실행자 서비스에는 폐기되지 않는 일정한 수의 "기본" 스레드가 있다.모든 스레드가 사용 중이면 서비스는 허용된 추가 스레드 수를 할당하고, 이 스레드는 특정 만료 시간에 사용되지 않으면 나중에 삭제된다.스레드가 더 이상 허용되지 않으면 태스크를 대기열에 배치할 수 있다.마지막으로, 이 대기열이 너무 길어질 경우 요청 스레드를 일시 중단하도록 구성할 수 있다.
공중의 계급 풀링 오브젝트 { 공중의 끈 온도1; 공중의 끈 temp2; 공중의 끈 온도3; 공중의 끈 getTemp1() { 돌아오다 온도1; } 공중의 공허하게 하다 setTemp1(끈 온도1) { 이.온도1 = 온도1; } 공중의 끈 getTemp2() { 돌아오다 temp2; } 공중의 공허하게 하다 setTemp2(끈 temp2) { 이.temp2 = temp2; } 공중의 끈 getTemp3() { 돌아오다 온도3; } 공중의 공허하게 하다 setTemp3(끈 온도3) { 이.온도3 = 온도3; } }
공중의 계급 풀링 오브젝트 풀 { 사유의 정태의 장기의 expTime = 6000;//6초 공중의 정태의 해시맵<풀링 오브젝트, 긴> 이용할 수 있는 = 새로운 해시맵<풀링 오브젝트, 긴>(); 공중의 정태의 해시맵<풀링 오브젝트, 긴> 사용하지 않음 = 새로운 해시맵<풀링 오브젝트, 긴>(); 공중의 동기화된 정태의 풀링 오브젝트 getObject() { 장기의 지금 당장 = 시스템.currentTimeMillis(); 만일 (!이용할 수 있는.비어 있음()) { 을 위해 (지도.엔트리<풀링 오브젝트, 긴> 입장권 : 이용할 수 있는.엔트리 세트()) { 만일 (지금 당장 - 입장권.getValue() > expTime) { //개체가 만료됨 팝피 엘리먼트(이용할 수 있는); } 다른 { 풀링 오브젝트 포를 뜨다 = 팝피 엘리먼트(이용할 수 있는, 입장권.getKey()); 밀다(사용하지 않음, 포를 뜨다, 지금 당장); 돌아오다 포를 뜨다; } } } // PollatedObject를 사용할 수 없거나 각각 만료되었으므로 새 개체를 반환하십시오. 돌아오다 createPooledObject(지금 당장); } 사유의 동기화된 정태의 풀링 오브젝트 createPooledObject(장기의 지금 당장) { 풀링 오브젝트 포를 뜨다 = 새로운 풀링 오브젝트(); 밀다(사용하지 않음, 포를 뜨다, 지금 당장); 돌아오다 포를 뜨다; } 사유의 동기화된 정태의 공허하게 하다 밀다(해시맵<풀링 오브젝트, 긴> 지도를 그리다, 풀링 오브젝트 포를 뜨다, 장기의 지금 당장) { 지도를 그리다.놓다(포를 뜨다, 지금 당장); } 공중의 정태의 공허하게 하다 releaseObject(풀링 오브젝트 포를 뜨다) { 정리하다(포를 뜨다); 이용할 수 있는.놓다(포를 뜨다, 시스템.currentTimeMillis()); 사용하지 않음.제거하다(포를 뜨다); } 사유의 정태의 풀링 오브젝트 팝피 엘리먼트(해시맵<풀링 오브젝트, 긴> 지도를 그리다) { 지도.엔트리<풀링 오브젝트, 긴> 입장권 = 지도를 그리다.엔트리 세트().반복기().다음에(); 풀링 오브젝트 핵심을= 입장권.getKey(); //긴 값=entry.getValue(); 지도를 그리다.제거하다(입장권.getKey()); 돌아오다 핵심을; } 사유의 정태의 풀링 오브젝트 팝피 엘리먼트(해시맵<풀링 오브젝트, 긴> 지도를 그리다, 풀링 오브젝트 핵심을) { 지도를 그리다.제거하다(핵심을); 돌아오다 핵심을; } 공중의 정태의 공허하게 하다 정리하다(풀링 오브젝트 포를 뜨다) { 포를 뜨다.setTemp1(무효의); 포를 뜨다.setTemp2(무효의); 포를 뜨다.setTemp3(무효의); } }
참고 항목
메모들
- ^ a b Goetz, Brian (2005-09-27). "Java theory and practice: Urban performance legends, revisited". IBM. IBM developerWorks. Archived from the original on 2012-02-14. Retrieved 2021-03-15.
참조
- Kircher, Michael; Prashant Jain (2002-07-04). "Pooling Pattern" (PDF). EuroPLoP 2002. Germany. Retrieved 2007-06-09.
- Goldshtein, Sasha; Zurbalev, Dima; Flatow, Ido (2012). Pro .NET Performance: Optimize Your C# Applications. Apress. ISBN 978-1-4302-4458-5.