2009-11-18 2 views
2

Java에서 각 객체에는 동기화 모니터가 있습니다. 그래서 구현은 꽤 메모리 사용량의 관점에서 응축되어 잘하면 빠른 것 같아요.C++에서 Java와 유사한 객체 모니터를 가장 효율적으로 구현하는 방법은 무엇입니까?

C++에 이식 할 때 가장 적합한 구현 방법은 무엇입니까? 나는 "pthread_mutex_init"보다 더 좋은 것이 있거나 자바의 객체 오버 헤드가 정말로 그렇게 높다고 생각한다.

편집 : Linux i386의 pthread_mutex_t가 24 바이트 큽니다. 각 개체에 대해이 공간을 예약해야한다면 엄청나게 큽니다.

+0

C++의 스레딩은 플랫폼에 따라 다르므로 어떤 플랫폼을 사용할지 지정할 수 있습니다. pthread를 사용하는 솔루션은 Win32 동기화 기능을 사용하는 솔루션과 다릅니다. –

+0

그럼 모든 주요 플랫폼 - MacOSX, Solaris, Windows, Linux. – Lothar

답변

2

The Sun Hotspot JVM implements thin locks using compare and swap. 오브젝트가 락되고있는 경우, 대기 중의 thread는 오브젝트를 잠근 thread의 모니터로 대기합니다. 즉, 스레드 당 무거운 잠금 장치 하나만 ​​있으면됩니다.

+0

그건 우리가 좋아하는거야. 모든 포인터는 플래그에 대해 다시 사용되는 하위 세 비트가 있어야합니다 ;-) –

+0

이것은 클래스가 깨우기위한 스레드 목록을 유지하고 알림이 호출 될 때 각 스레드에 도달해야 함을 의미합니다. 이 구현은 pthread가 아닌 모든 시스템만큼 빠를 수 있습니다. –

3

실제로는 pthread_mutex_init보다 더 나쁩니다. Java의 대기/통보 때문에 모니터를 구현하려면 쌍을 이루는 뮤텍스와 조건 변수가 필요합니다.

실제로 JVM을 구현할 때 책에서 가능한 모든 플랫폼 별 최적화를 적용한 다음 새 모니터를 만들어 모니터를 최대한 빨리 만듭니다. 정말 불쾌한 일을 할 수 없다면 확실히 가비지 수집을 최적화 할 수 없습니다 .-)

모든 개체가 자체 모니터를 가질 필요는 없습니다. 현재 동기화되지 않은 객체는 필요하지 않습니다. 따라서 JVM은 모니터 풀을 생성 할 수 있으며 각 객체는 포인터 필드를 가질 수 있습니다. 포인터 필드는 스레드가 실제로 객체에서 동기화하려고 할 때 채워집니다 (예 : 플랫폼 별 원자 비교 및 ​​스왑 연산 사용). 따라서 모니터 초기화 비용은 객체 생성 비용을 추가 할 필요가 없습니다. 메모리가 미리 지워져 있다고 가정하면 객체 생성은 포인터를 감소시키고 (gc를 실행하는 코드에 예측 된 거짓 분기를 갖는 일종의 경계 검사를 더한 것); 유형을 기입하십시오. 가장 파생 된 생성자를 호출합니다. Object의 생성자가 아무 일도하지 않도록 준비 할 수 있다고 생각하지만 구현에 따라 많은 영향을받는 것은 분명합니다.

실제로는 평균 Java 응용 프로그램이 매우 많은 객체에서 동시에 동기화되지 않으므로 모니터 풀이 시간과 메모리에서 잠재적으로 큰 최적화가 될 수 있습니다.

+0

모든 단일 메서드에 동기화를 추가하는 나쁜 프로그래머 코드가 많은 경우가 아니라면. 그러나 나는 당신의 생각을 좋아합니다. 관련된 객체와 메모리 오버 헤드를 찾기 위해 해시 테이블 조회 거래. – Lothar

+0

JVM을 구현 한 (상당히 오래된) 경험에서 나쁜 코더는 프로그램에 의해 생성 된 대부분의 객체가 문자열임을 보장합니다. 따라서 모든 메서드를 동기화하고 교착 상태를 피할지라도 모니터를 필요로하는 개체는 소수에 지나지 않습니다. 나는 우리 JVM이 brianegge의 링크처럼 얇은 잠금을 사용한다고 생각하지 않는다 : 객체는 모니터를 가졌거나 그렇지 않았다. 우리는 당시 Pnava/J2ME의 경우 Sun보다 빠르지 만 핫스팟의 영리한 내용이 모바일에 도달하기 전에는 KVM이 두려워졌습니다. –

2

자바가 어떻게 동작하는지 모르겠지만 .NET은 뮤텍스 (또는 아날로그 -이 구조를 "syncblk"라고 부르는 구조체)를 객체에 직접 저장하지 않습니다. 오히려, 그것은 syncblks의 전역 테이블을 가지고 있으며, 객체는 그 테이블의 인덱스에 의해 syncblk를 참조합니다. 게다가 객체는 생성하자마자 동기 블록을 얻지 못합니다. 대신 객체는 첫 번째 잠금에서 필요할 때 생성됩니다.

내가 가정 (참고, 나는 그것이 실제로 어떻게하는지 몰라!)는 스레드 안전한 방법으로 개체와 개체의 syncblk 연결하는 원자 비교 및 ​​교환을 사용 :

  1. 확인 숨겨진 syncblk_index 0에 대한 객체의 필드입니다. 0이 아니면 잠금을 설정하고 계속합니다. 그렇지 않으면 ...
  2. 글로벌 테이블에서 새 syncblk을 만들고 인덱스를 가져옵니다 (글로벌 잠금은 필요에 따라 여기에서 획득/릴리스됩니다).).
  3. 개체 자체에 쓰기 및 쓰기를 비교 및 ​​교환합니다.
  4. 이전 값이 0 인 경우 (0은 유효한 색인이 아니며 객체의 숨겨진 syncblk_index 필드의 초기 값임), syncblk 생성에 대한 논쟁은 없습니다. 잠그고 계속하십시오.
  5. 이전 값이 0이 아니라면 다른 누군가가 이미 syncblk를 만들고이를 생성하는 동안 그 객체와 연관 지어 왔고 이제 우리는 그 syncblk의 색인을 갖게되었습니다. 방금 작성한 방폐 물을 버리고 획득 한 방패를 잠급니다.

따라서 개체 당 오버 헤드는 4 최상의 경우 (syncblk 테이블에 32 비트 인덱스를 가정) 바이트, 실제로 고정 된 개체 크다. 객체를 거의 잠그지 않으면이 스킴은 리소스 사용을 줄이는 좋은 방법입니다. 그러나 대부분의 또는 모든 객체를 잠글 필요가 있다면 객체 내에 직접 뮤텍스를 저장하는 것이 더 빠를 수도 있습니다.

+0

"궁극적으로 또는 대부분의 개체를 잠글 필요가 있다면 ..."그러면 프로그램을 빨리 실행할 필요가 없습니다 .-) –

+0

더 구체적이어야합니다. 물론 특정 개체 클래스, 일반적으로 모든 객체가 아닙니다. 특정 클래스의 오브젝트의 경우, 어떤 시점에서 그 클래스의 모든 인스턴스가 잠겨져 있어야한다는 것이 확실합니다. 그렇다면 객체 내에 직접 뮤텍스를 할당하는 것이 더 합리적 일 수 있습니다. –

1

마다 이러한 모니터가 필요하지 않습니다. 개체마다!

Java에서 C++로 포팅 할 때 모든 것을 맹목적으로 복사하는 것은 나쁘다고 생각합니다. 자바를위한 최상의 구조는 C++을위한 최선의 구조와는 다르다. 적어도 자바는 가비지 콜렉션을 갖고 C++은 그렇지 않기 때문이다.

정말 필요한 개체에만 모니터를 추가하십시오. 유형의 일부 인스턴스 만 동기화가 필요한 경우에는 동기화에 필요한 뮤텍스 (및 조건 변수)가 포함 된 래퍼 클래스를 만드는 것이 어렵지 않습니다. 다른 사람들은 이미 말했듯이 대안은 객체 주소의 해시를 사용하여 배열을 색인하는 것과 같이 각 객체에 대해 하나를 선택하는 것과 함께 동기화 객체 풀을 사용하는 것입니다.

각 차례마다 플랫폼 특성에 의존하기보다는 이식성을 높이기 위해 부스트 스레드 라이브러리 또는 새로운 C++ 0x 표준 스레드 라이브러리를 사용하려고합니다. Boost.Thread은 Linux, MacOSX, win32, Solaris, HP-UX 등을 지원합니다. 내 implementation of the C++0x thread library은 현재 Windows 및 Linux 만 지원하지만 다른 구현은 곧 제공 될 예정입니다.