.NET에서 아이스 캔디를 고정하는 방법 (클래스를 변경 불가능하게 만들기)
메인 스레드가 구성을 완료 한 후 읽기 전용으로 만들려는 클래스를 설계하고 있습니다. 즉, "고정"합니다. Eric Lippert는 이것을 불변성 아이스 라고 부릅니다 . 고정 된 후에는 여러 스레드에서 동시에 읽기 위해 액세스 할 수 있습니다.
제 질문은 이것을 현실적으로 효율적인 스레드 안전한 방식으로 작성하는 방법입니다 . 즉, 불필요하게 영리 해 지려고하지 않습니다 .
시도 1 :
public class Foobar
{
private Boolean _isFrozen;
public void Freeze() { _isFrozen = true; }
// Only intended to be called by main thread, so checks if class is frozen. If it is the operation is invalid.
public void WriteValue(Object val)
{
if (_isFrozen)
throw new InvalidOperationException();
// write ...
}
public Object ReadSomething()
{
return it;
}
}
Eric Lippert는 이 게시물 에서 이것이 괜찮을 것이라고 제안하는 것 같습니다 . 쓰기에 릴리스 의미가 있다는 것을 알고 있지만 이것이 이해하는 한 ordering 에만 관련되며 모든 스레드가 쓰기 직후에 값을 볼 수 있다는 것을 반드시 의미하지는 않습니다. 누구든지 이것을 확인할 수 있습니까? 이것은이 솔루션이 스레드로부터 안전하지 않다는 것을 의미합니다 (물론 이것이 유일한 이유는 아닐 수도 있습니다).
시도 2 :
위 Interlocked.Exchange
의 값이 실제로 게시되었는지 확인하기 위해 사용 :
public class Foobar
{
private Int32 _isFrozen;
public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); }
public void WriteValue(Object val)
{
if (_isFrozen == 1)
throw new InvalidOperationException();
// write ...
}
}
여기서 장점은 모든 읽기에 대한 오버 헤드없이 값이 게시된다는 것입니다. Interlocked 메서드가 전체 메모리 장벽을 사용하므로 _isFrozen에 쓰기 전에 읽기가 이동되지 않으면 이것이 스레드로부터 안전하다고 생각합니다. 그러나 컴파일러가 무엇을 할 것인지 (그리고 상당히 많이 보이는 C # 사양의 섹션 3.10에 따르면) 누가 이것이 스레드로부터 안전한지 알 수 없습니다.
시도 3 :
또한 Interlocked
.
public class Foobar
{
private Int32 _isFrozen;
public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); }
public void WriteValue(Object val)
{
if (Interlocked.CompareExchange(ref _isFrozen, 0, 0) == 1)
throw new InvalidOperationException();
// write ...
}
}
확실히 스레드 안전하지만 모든 읽기에 대해 비교 교환을 수행해야하는 것은 약간 낭비 인 것 같습니다. 이 오버 헤드가 아마도 최소한이라는 것을 알고 있지만 합리적으로 효율적인 방법을 찾고 있습니다 (아마도이 방법 일 수도 있습니다).
시도 4 :
사용 volatile
:
public class Foobar
{
private volatile Boolean _isFrozen;
public void Freeze() { _isFrozen = true; }
public void WriteValue(Object val)
{
if (_isFrozen)
throw new InvalidOperationException();
// write ...
}
}
그러나 Joe Duffy는 " sayonara volatile "을 선언 했으므로 이것이 해결책이라고 생각하지 않습니다.
시도 5 :
모든 것을 잠그고 약간 과잉 인 것 같습니다.
public class Foobar
{
private readonly Object _syncRoot = new Object();
private Boolean _isFrozen;
public void Freeze() { lock(_syncRoot) _isFrozen = true; }
public void WriteValue(Object val)
{
lock(_syncRoot) // as above we could include an attempt that reads *without* this lock
if (_isFrozen)
throw new InvalidOperationException();
// write ...
}
}
또한 확실히 스레드 안전 해 보이지만 위의 Interlocked 접근 방식을 사용하는 것보다 오버 헤드가 더 많으므로이 방법보다 시도 3을 선호합니다.
그런 다음 적어도 몇 가지를 더 생각해 낼 수 있습니다 (더 많은 것이 있다고 확신합니다).
시도 6 : 사용 Thread.VolatileWrite
및 Thread.VolatileRead
, 그러나 이것들은 약간 무거운 편입니다.
시도 7 : 사용 Thread.MemoryBarrier
, 너무 내부적으로 보입니다 .
시도 8 : 변경 불가능한 사본 만들기-원하지 않음
요약 :
- 어떤 시도를 사용하고 그 이유는 무엇입니까 (또는 완전히 다른 경우 어떻게 수행 하시겠습니까)? (즉, 지나치게 "영리"하지 않고 합리적으로 효율적이면서 동시에 읽은 값을 게시하는 가장 좋은 방법은 무엇입니까?)
- .NET의 메모리 모델 "릴리스"쓰기 의미는 다른 모든 스레드가 업데이트 (캐시 일관성 등)를 볼 수 있음을 의미합니까? 나는 일반적으로 이것에 대해 너무 많이 생각하고 싶지 않지만 이해하는 것이 좋습니다.
편집하다:
아마도 내 질문은 명확하지 않았지만 특히 위의 시도가 좋은지 나쁜지에 대한 이유를 찾고 있습니다. 여기서는 쓰기를 한 다음 동시 읽기 전에 고정되는 단일 작성자의 시나리오에 대해 이야기하고 있습니다. 시도 1은 괜찮다고 생각하지만 그 이유를 정확히 알고 싶습니다 (예를 들어 읽기를 어떻게 든 최적화 할 수 있는지 궁금합니다). 나는 이것이 좋은 디자인 관행인지 아닌지보다는 실제 스레딩 측면에 더 관심이 있습니다.
응답에 대한 많은 감사는 질문 받았지만, 나는 주어진 답변을하지 않는 것이 느끼기 때문에 대답으로 자신이 표시하도록 선택한 확실히 내 질문에 대답하고 나는 표시하는 사이트를 방문하는 사람에게 인상을주고 싶지 않아 바운티 만료로 인해 자동으로 표시 되었기 때문에 대답은 정확합니다. 또한 가장 많은 득표를 한 답변이 압도적으로 뽑혔다 고 생각하지 않으며 자동으로 답변으로 표시하기에 충분하지 않습니다.
나는 여전히 # 1이 올바른 시도를하려고하지만 권위있는 답변을 좋아했을 것입니다. 나는 x86이 강력한 모델을 가지고 있다는 것을 이해하지만, 특정 아키텍처를위한 코딩을하고 싶지는 않다 (그리고해서는 안된다). 그것은 결국 .NET의 좋은 점 중 하나이다.
대답이 확실하지 않은 경우 잠금에 대한 많은 경합을 피하기 위해 여기에 표시된 최적화를 사용하여 잠금 방법 중 하나를 사용하십시오.
주제에서 약간 벗어 났지만 호기심에서 나온 것일 수도 있습니다. :) 왜 "진짜"불변성을 사용하지 않습니까? 예를 들어, Freeze ()는 변경 불가능한 복사본을 반환하고 ( "쓰기 메서드"나 내부 상태를 변경할 수있는 다른 가능성없이) 원본 객체 대신이 복사본을 사용합니다. 상태를 변경하지 않고 대신 각 쓰기 작업에서 새 복사본 (변경된 상태 포함)을 반환 할 수도 있습니다 (afaik 문자열 클래스가이 작업을 수행함). "실제 불변성"은 본질적으로 스레드로부터 안전합니다.
시도 5에 투표하고 lock (this) 구현을 사용합니다.
이것이이 작업을 수행하는 가장 신뢰할 수있는 방법입니다. 리더 / 라이터 잠금을 사용할 수는 있지만 이득은 거의 없습니다. 일반 잠금 장치를 사용하십시오.
필요한 경우 먼저 확인한 _isFrozen
다음 잠 가서 '고정'성능을 향상시킬 수 있습니다 .
void Freeze() { lock (this) _isFrozen = true; }
object ReadValue()
{
if (_isFrozen)
return Read();
else
lock (this) return Read();
}
void WriteValue(object value)
{
lock (this)
{
if (_isFrozen) throw new InvalidOperationException();
Write(value);
}
}
다른 스레드에 표시하기 전에 개체를 실제로 만들고 채우고 고정하면 스레드 안전성을 처리하는 데 특별한 것이 필요하지 않으므로 (.NET의 강력한 메모리 모델이 이미 보장됨) 솔루션 1은 다음과 같습니다. 유효한.
그러나 고정되지 않은 개체를 다른 스레드에 제공하는 경우 (또는 사용자가 클래스를 사용하는 방법을 알지 못하고 간단하게 클래스를 만드는 경우) 버전을 사용하여 완전히 변경 불가능한 새 인스턴스를 반환하는 솔루션이 더 좋습니다. 이 경우 Mutable 인스턴스는 StringBuilder와 같고 불변 인스턴스는 문자열과 같습니다. 추가 보증이 필요한 경우 변경 가능한 인스턴스는 작성자 스레드를 확인하고 다른 스레드에서 사용되는 경우 예외를 throw 할 수 있습니다 (모든 메서드에서 가능한 부분 읽기를 방지하기 위해 ...).
시도 2는 강력한 메모리 모델을 가진 x86 및 기타 프로세서에서 스레드로부터 안전하지만, 소비 된 코드 내에서 효율적으로 수행 할 수있는 방법이 없기 때문에 스레드 안전성을 소비자 문제로 만드는 방법이 있습니다. 중히 여기다:
if(!foo.frozen)
{
foo.apropery = "avalue";
}
frozen
속성 의 스레드 안전 및 apropery
의 setter 의 가드 코드 는 완벽하게 스레드로부터 안전하더라도 여전히 경쟁 조건이 있기 때문에 실제로 중요하지 않습니다. 대신 나는 그것을 다음과 같이 쓸 것이다.
lock(foo)
{
if(!foo.frozen)
{
foo.apropery = "avalue";
}
}
본질적으로 스레드로부터 안전하지 않은 속성이 없습니다.
# 1-스레드가 안전하지 않은 리더-작성자가 아닌 리더 측에 문제가 있다고 생각합니다 (코드가 표시되지 않음)
# 2-스레드가 안전하지 않은 리더-# 1과 동일
# 3-대부분의 경우에 대해 읽기 검사를 최적화 할 수 있습니다. CPU 캐시가 동기화 됨)
시도 3 :
또한 Interlocked를 사용하여 읽기를 수행하십시오.
public class Foobar {
private object _syncRoot = new object();
private int _isFrozen = 0; // perf compiler warning, but training code, so show defaults
// Why Exchange to 1 then throw away result. Best to just increment.
//public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); }
public void Freeze() { Interlocked.Increment(ref _isFrozen); }
public void WriteValue(Object val) {
// if this core can see _isFrozen then no special lock or sync needed
if (_isFrozen != 0)
throw new InvalidOperationException();
lock(_syncRoot) {
if (_isFrozen != 0)
throw new InvalidOperationException(); // the 'throw' is 100x-1000x more costly than the lock, just eat it
_val = val;
}
}
public object Read() {
// frozen is one-way, if one-way state has been published
// to my local CPU cache then just read _val.
// There are very strange corner cases when _isFrozen and _val fields are in
// different cache lines, but should be nearly impossible to hit unless
// dealing with very large structs (make it more likely to cross
// 4k cache line).
if (_isFrozen != 0)
return _val;
// else
lock(_syncRoot) { // _isFrozen is 0 here
if (_isFrozen != 0) // if _isFrozen is 1 here we just collided with writer using lock on other thread, or our CPU cache was out of sync and lock() forced the dirty cache line to be read from main memory
return _val;
throw new InvalidOperationException(); // throw is 100x-1000x more expensive than lock, eat the cost of lock
}
}
}
'휘발성은 죽었다'에 대한 Joe Duffy의 게시물은 그의 차세대 CLR / OS 아키텍처와 ARM의 CLR에 대한 것입니다. 멀티 코어 x64 / x86을하는 사람들은 휘발성이 좋다고 생각합니다. perf가 주요 관심사라면 위의 코드를 측정하고 휘발성과 비교하는 것이 좋습니다.
답변을 게시하는 다른 사람들과 달리 독자가 많으면 (동시에 동일한 객체를 읽을 가능성이있는 3 개 이상의 스레드) lock ()으로 바로 이동하지 않습니다. 그러나 샘플에서는 충돌이 발생할 때 예외와 성능에 민감한 질문을 혼합합니다. 예외를 사용하는 경우 다른 상위 수준 구조를 사용할 수도 있습니다.
완전한 안전을 원하지만 많은 동시 판독기에 대해 최적화해야하는 경우 lock () / Monitor를 ReaderWriterLockSlim으로 변경하십시오.
.NET에는 게시 값을 처리하는 새로운 기본 요소가 있습니다. Rx를 살펴보십시오. 어떤 경우에는 매우 빠르고 잠금이 없을 수 있습니다 (위와 유사한 최적화를 사용한다고 생각합니다).
여러 번 작성되었지만 하나의 값만 유지되는 경우-Rx에서 "new ReplaySubject (bufferSize : 1)"입니다. 시도해 보면 얼마나 빠른지 놀랄 것입니다. 동시에이 수준의 세부 사항을 배우려는 시도에 박수를 보냅니다.
잠금 장치없이 가고 싶다면 Thread.MemoryBarrier ()에 대한 혐오감을 극복하십시오. 매우 중요합니다. 그러나 Joe Duffy가 설명한 것처럼 휘발성과 동일한 문제가 있습니다. 메모리 읽기 순서 변경을 방지하기 위해 컴파일러 및 CPU에 대한 힌트로 설계되었습니다 (CPU 용어로 오랜 시간이 걸리기 때문에 힌트 있음). 이 재정렬이 함수의 자동 인라인과 같은 CLR 구조와 결합되면 메모리 및 레지스터 수준에서 매우 놀라운 동작을 볼 수 있습니다. MemoryBarrier ()는 CPU와 CLR이 대부분의 시간을 사용하는 단일 스레드 메모리 액세스 가정을 비활성화합니다.
아마도 내 질문은 명확하지 않았지만 특히 위의 시도가 좋은지 나쁜지에 대한 이유를 찾고 있습니다. 여기서는 쓰기를 한 다음 동시 읽기 전에 고정되는 단일 작성자의 시나리오에 대해 이야기하고 있습니다. 시도 1은 괜찮다고 생각하지만 그 이유를 정확히 알고 싶습니다 (예를 들어 읽기를 어떻게 든 최적화 할 수 있는지 궁금합니다). 나는 이것이 좋은 디자인 관행인지 아닌지보다는 실제 스레딩 측면에 더 관심이 있습니다.
좋아, 이제 나는 당신이 무엇을하고 있고 응답에서 찾고 있는지 더 잘 이해합니다. 먼저 각 시도를 해결하여 잠금 사용을 장려하는 이전 답변에 대해 자세히 설명하겠습니다.
시도 1 :
어떤 형태의 동기화 프리미티브도없는 간단한 클래스를 사용하는 접근 방식은 귀하의 예제에서 전적으로 실행 가능합니다. 'authoring'스레드는 변경 상태 동안이 클래스에 액세스 할 수있는 유일한 스레드이므로 안전해야합니다. 다른 스레드가 클래스가 '고정'되기 전에 액세스 할 수있는 가능성이있는 경우에만 동기화를 제공해야합니다. 기본적으로 스레드가 본 적이없는 캐시를 가질 수는 없습니다.
이 목록의 내부 상태에 대한 캐시 된 복사본이있는 스레드 외에 관심을 가져야 할 또 다른 동시성 문제가 있습니다. 작성 스레드에 의한 쓰기 재정렬을 고려해야합니다. 예제 솔루션에는이 문제를 해결할 수있는 코드가 충분하지 않지만이 '고정'목록을 다른 스레드에 전달하는 프로세스가 문제의 핵심입니다. Interlocked.Exchange를 사용하거나 휘발성 상태에 쓰고 있습니까?
나는 다른 스레드가 변이하는 동안 인스턴스를 보지 못했다는 보장이 없기 때문에 이것이 최선의 접근 방식 이 아니라고 주장합니다 .
시도 2 :
시도 2는 사용해서는 안됩니다. 구성원에 원자 적 쓰기를 사용하는 경우 원자 적 읽기도 사용해야합니다. 읽기와 쓰기가 모두 원자 적이 지 않고는 아무것도 얻지 못했기 때문에 다른 하나 없이는 절대 추천하지 않습니다. 원자 읽기 및 쓰기의 올바른 적용은 '시도 3'입니다.
시도 3 :
스레드가 고정 된 목록을 변경하려고 시도하면 예외가 throw됩니다. 그러나 읽기가 고정 된 인스턴스에서만 허용된다는 주장은하지 않습니다. IMHO는 _isFrozen
원자 및 비 원자 접근 자로 변수에 액세스하는 것만큼이나 나쁩니다 . 쓰기를 보호하는 것이 중요하다고 말하려는 경우 항상 읽기를 보호해야합니다. 다른 하나가없는 것은 '이상한'것입니다.
어리석은 코드를 작성하지만 읽지 않는 코드를 작성하는 것에 대한 내 감정을 간과하는 것은 특정 용도를 고려할 때 허용되는 접근 방식입니다. 한 명의 작가가 있고, 글을 쓰고, 멈춘 다음 독자에게 제공합니다. 이 시나리오에서는 코드가 올바르게 작동합니다. _isFrozen
클래스를 다른 스레드에 전달하기 전에 필요한 메모리 장벽을 제공하기 위해 세트에 대한 원자 적 연산에 의존 합니다.
간단히 말해서이 접근 방식은 작동하지만 스레드에 고정되지 않은 인스턴스가 있으면 다시 중단됩니다.
시도 4 :
마음에 이것은 시도 3 (한 명의 작가가 주어진 경우)과 거의 동일하지만 한 가지 큰 차이점이 있습니다. 이 예 _isFrozen
에서 리더를 체크인하면 모든 액세스에 메모리 배리어가 필요합니다. 목록이 고정되면 불필요한 오버 헤드가 발생합니다.
그래도 _isFrozen
읽기 중에의 상태에 대해 어설 션이 작성되지 않으므로 예제 사용에서 성능이 동일해야한다는 점에서 시도 3과 동일한 문제 가 있습니다.
시도 5 :
내가 말했듯이 이것은 내 다른 대답에 나타나는 것처럼 읽는 수정을 고려할 때 선호하는 것입니다.
시도 6 :
본질적으로 # 4와 동일합니다.
시도 7 :
.NET Framework로 특정 요구 사항을 해결할 수 있습니다 Thread.MemoryBarrier
. 기본적으로 시도 1의 코드를 사용하여 인스턴스를 만들고를 호출하고을 Freeze()
추가 Thread.MemoryBarrier
한 다음 인스턴스를 공유 (또는 잠금 내에서 공유)합니다. 이것은 제한된 사용 사례에서만 잘 작동합니다.
시도 8 :
이것에 대해 더 많이 알지 못하면 사본 비용에 대해 조언 할 수 없습니다.
요약
다시 말하지만 저는 스레딩 보장이 있거나 전혀없는 클래스를 사용하는 것을 선호합니다. '부분적으로'스레드로부터 안전한 클래스를 만드는 것은 IMO가 위험합니다.
유명한 제다이 마스터의 말 :
시도하거나하지 마십시오.
스레드 안전성도 마찬가지입니다. 클래스는 스레드로부터 안전해야합니다. 이 접근 방식을 사용하면 시도 5의 기능 보강을 사용하거나 시도 7을 사용하게됩니다. 선택이 주어지면 7 번을 권장하지 않습니다.
따라서 내 권장 사항은 완전히 스레드로부터 안전한 버전 뒤에 있습니다. 둘 사이의 성능 비용은 매우 적기 때문에 거의 존재하지 않습니다. 독자 스레드는 단순히 단일 작성자가있는 사용 시나리오 때문에 잠금에 도달 하지 않습니다 . 그러나 그렇게한다면 올바른 행동은 여전히 확실합니다. 따라서 시간이 지남에 따라 코드가 변경되고 인스턴스가 동결되기 전에 갑자기 공유되므로 프로그램이 충돌하는 경합 상태가 발생하지 않습니다. 스레드가 안전하든 그렇지 않든, 하프 인하지 마십시오. 언젠가는 깜짝 놀라게됩니다.
내가 선호하는 것은 둘 이상의 스레드가 공유하는 모든 클래스가 두 가지 유형 중 하나입니다.
- 완전히 불변입니다.
- 스레드로부터 완전히 안전합니다.
팝 시클 목록은 디자인 상 변경 불가능하지 않기 때문에 # 1에 맞지 않습니다. 따라서 스레드간에 객체를 공유하려면 # 2에 맞아야합니다.
이 모든 소리가 내 추론을 더 설명하기를 바랍니다. :)
_syncRoot
많은 사람들이 _syncRoot
잠금 구현 에서 a의 사용을 건너 뛰었다는 것을 알아 차 렸습니다 . _syncRoot를 사용하는 이유는 유효하지만 항상 필요한 것은 아닙니다. 단일 작성자가있는 예제 사용에서는에 대해 lock(this)
다른 힙 할당을 추가하지 않고도을 사용 하면 충분합니다 _syncRoot
.
사물이 구성되고 기록 된 다음 영구적으로 고정되고 여러 번 읽혀 지는가?
아니면 여러 번 고정 및 고정 해제하고 다시 고정합니까?
전자 인 경우 "고정됨"검사는 기록기 메서드가 아닌 판독기 메서드에 있어야합니다 (고정되기 전에 읽기를 방지하기 위해).
또는 후자의 경우주의해야 할 사용 사례는 다음과 같습니다.
- 메인 쓰레드는 writer 메소드를 호출하고 그것이 고정되지 않았 음을 발견하고 따라서 쓰기를 시작합니다.
- 쓰기가 완료되기 전에 다른 (주) 스레드가 여전히 쓰기를하는 동안 누군가가 개체를 고정한 다음 읽기를 시도합니다.
후자의 경우 Google은 흥미로울 수있는 여러 독자의 단일 작성자에 대해 많은 결과를 표시합니다 .
일반적으로 각 변경 가능한 객체에는 명확하게 정의 된 "소유자"가 정확히 하나 있어야합니다. 공유 객체는 불변이어야합니다. 아이스 캔디는 고정 될 때까지 여러 스레드에서 접근 할 수 없습니다.
개인적으로 나는 "동결"방법이 노출 된 아이스바 면역을 좋아하지 않는다. 더 깨끗한 접근 방식은 AsMutable
및 AsImmutable
메서드를 갖는 것입니다 (각각은 적절할 때 수정되지 않은 개체를 단순히 반환합니다). 이러한 접근 방식은 불변성에 대한보다 강력한 약속을 허용 할 수 있습니다. 예를 들어, AsImmutable
멤버가 호출되는 동안 "공유되지 않는 변경 가능한 객체"가 변경 되는 경우 ( "공유되지 않는"객체와 반대되는 동작) 복사본의 데이터 상태는 불확실 할 수 있지만 반환 된 것은 무엇이든 불변입니다. 대조적으로, 한 스레드가 객체를 동결 한 다음 다른 스레드가 작성하는 동안 변경 불가능하다고 가정하면 "불변"객체는 동결되고 해당 값을 읽은 후 변경 될 수 있습니다.
편집하다
추가 설명을 기반으로 모니터 잠금 내에서 개체에 쓰는 코드를 사용하고 고정 루틴을 다음과 같이 표시하는 것이 좋습니다.
public Thingie Freeze (void) // 해당 객체를 반환합니다. { if (isFrozen) // 비공개 필드 이것을 반환하십시오; 그밖에 return DoFreeze (); } Thingie DoFreeze (무효) { if (Monitor.TryEnter (무엇이든)) { isFrozen = true; 이것을 반환하십시오; } 그렇지 않으면 (isFrozen) 이것을 반환하십시오; 그밖에 throw new InvalidOperationException ( "작성자가 사용중인 개체"); }
이 Freeze
메서드는 스레드 수에 관계없이 여러 번 호출 될 수 있습니다. 인라인 할 수있을만큼 짧아야하며 (프로파일 링하지는 않았지만) 실행하는 데 거의 시간이 걸리지 않습니다. 어떤 스레드에서 객체에 대한 첫 번째 액세스가 Freeze
메서드 를 통해 이루어지는 경우 합리적인 메모리 모델에서 적절한 가시성을 보장해야합니다 (스레드가 객체를 생성하고 원래 동결 한 스레드가 수행 한 객체에 대한 업데이트를 스레드가 보지 못하더라도, TryEnter
메모리 장벽을 보장하는을 수행하고 실패한 후에는 객체가 고정되었음을 확인하고 반환합니다.
객체를 작성할 코드가 먼저 잠금을 획득하면 고정 된 객체에 대한 쓰기 시도가 교착 상태가 될 수 있습니다. 그러한 코드가 예외를 TryEnter
던지도록하려면 잠금을 얻을 수 없으면 예외를 사용 하고 던집니다.
잠금에 사용되는 개체는 고정 될 개체가 독점적으로 보유하는 것이어야합니다. 고정 될 개체가 어떤 것에 대한 순수 개인 참조를 보유하지 않는 경우, this
순수하게 잠금 목적으로 개인 개체를 잠 그거나 만들 수 있습니다 . 정리하지 않고 '입력 된'모니터 잠금을 버리는 것이 안전합니다. GC는 잠금에 대한 참조가 없으면 누구도 잠금이 취소되었을 때 잠금이 입력되었는지 여부를 신경 쓰거나 물어볼 방법이 없기 때문에 단순히 잊어 버릴 것입니다.
다음 접근 방식이 비용 측면에서 어떻게 될지 확실하지 않지만 약간 다릅니다. 처음에는 여러 스레드가 동시에 값을 쓰려고하는 경우에만 잠금이 발생합니다. 일단 고정되면 이후의 모든 호출은 예외를 직접 가져옵니다.
시도 9 :
public class Foobar
{
private readonly Object _syncRoot = new Object();
private object _val;
private Boolean _isFrozen;
private Action<object> WriteValInternal;
public void Freeze() { _isFrozen = true; }
public Foobar()
{
WriteValInternal = BeforeFreeze;
}
private void BeforeFreeze(object val)
{
lock (_syncRoot)
{
if (_isFrozen == false)
{
//Write the values....
_val = val;
//...
//...
//...
//and then modify the write value function
WriteValInternal = AfterFreeze;
Freeze();
}
else
{
throw new InvalidOperationException();
}
}
}
private void AfterFreeze(object val)
{
throw new InvalidOperationException();
}
public void WriteValue(Object val)
{
WriteValInternal(val);
}
public Object ReadSomething()
{
return _val;
}
}
Lazy를 확인 했습니까?
http://msdn.microsoft.com/en-us/library/dd642331.aspx
ThreadLocal을 사용하는
http://msdn.microsoft.com/en-us/library/dd642243.aspx
그리고 실제로 더 멀리 보면 Freezable 클래스가 있습니다.
http://msdn.microsoft.com/en-us/library/vstudio/ms602734(v=vs.100).aspx
POST Sharp를 사용하여이를 달성 할 수 있습니다.
하나의 인터페이스를
public interface IPseudoImmutable
{
bool IsFrozen { get; }
bool Freeze();
}
다음과 같이 InstanceLevelAspect에서 속성을 파생하십시오.
/// <summary>
/// implement by divyang
/// </summary>
[Serializable]
[IntroduceInterface(typeof(IPseudoImmutable),
AncestorOverrideAction = InterfaceOverrideAction.Ignore, OverrideAction = InterfaceOverrideAction.Fail)]
public class PseudoImmutableAttribute : InstanceLevelAspect, IPseudoImmutable
{
private volatile bool isFrozen;
#region "IPseudoImmutable"
[IntroduceMember]
public bool IsFrozen
{
get
{
return this.isFrozen;
}
}
[IntroduceMember(IsVirtual = true, OverrideAction = MemberOverrideAction.Fail)]
public bool Freeze()
{
if (!this.isFrozen)
{
this.isFrozen = true;
}
return this.IsFrozen;
}
#endregion
[OnLocationSetValueAdvice]
[MulticastPointcut(Targets = MulticastTargets.Property | MulticastTargets.Field)]
public void OnValueChange(LocationInterceptionArgs args)
{
if (!this.IsFrozen)
{
args.ProceedSetValue();
}
}
}
public class ImmutableException : Exception
{
/// <summary>
/// The location name.
/// </summary>
private readonly string locationName;
/// <summary>
/// Initializes a new instance of the <see cref="ImmutableException"/> class.
/// </summary>
/// <param name="message">
/// The message.
/// </param>
public ImmutableException(string message)
: base(message)
{
}
public ImmutableException(string message, string locationName)
: base(message)
{
this.locationName = locationName;
}
public string LocationName
{
get
{
return this.locationName;
}
}
}
다음과 같이 수업에 적용하십시오.
[PseudoImmutableAttribute]
public class TestClass
{
public string MyString { get; set; }
public int MyInitval { get; set; }
}
그런 다음 다중 스레드에서 실행
/// <summary>
/// The program.
/// </summary>
public class Program
{
/// <summary>
/// The main.
/// </summary>
/// <param name="args">
/// The args.
/// </param>
public static void Main(string[] args)
{
Console.Title = "Divyang Demo ";
var w = new Worker();
w.Run();
Console.ReadLine();
}
}
internal class Worker
{
private object SyncObject = new object();
public Worker()
{
var r = new Random();
this.ObjectOfMyTestClass = new MyTestClass { MyInitval = r.Next(500) };
}
public MyTestClass ObjectOfMyTestClass { get; set; }
public void Run()
{
Task readWork;
readWork = Task.Factory.StartNew(
action: () =>
{
for (;;)
{
Task.Delay(1000);
try
{
this.DoReadWork();
}
catch (Exception exception)
{
// Console.SetCursorPosition(80,80);
// Console.SetBufferSize(100,100);
Console.WriteLine("Read Exception : {0}", exception.Message);
}
}
// ReSharper disable FunctionNeverReturns
});
Task writeWork;
writeWork = Task.Factory.StartNew(
action: () =>
{
for (int i = 0; i < int.MaxValue; i++)
{
Task.Delay(1000);
try
{
this.DoWriteWork();
}
catch (Exception exception)
{
Console.SetCursorPosition(80, 80);
Console.SetBufferSize(100, 100);
Console.WriteLine("write Exception : {0}", exception.Message);
}
if (i == 5000)
{
((IPseudoImmutable)this.ObjectOfMyTestClass).Freeze();
}
}
});
Task.WaitAll();
}
/// <summary>
/// The do read work.
/// </summary>
public void DoReadWork()
{
// ThreadId where reading is done
var threadId = System.Threading.Thread.CurrentThread.ManagedThreadId;
// printing on screen
lock (this.SyncObject)
{
Console.SetCursorPosition(0, 0);
Console.SetBufferSize(290, 290);
Console.WriteLine("\n");
Console.WriteLine("Read Start");
Console.WriteLine("Read => Thread Id: {0} ", threadId);
Console.WriteLine("Read => this.objectOfMyTestClass.MyInitval: {0} ", this.ObjectOfMyTestClass.MyInitval);
Console.WriteLine("Read => this.objectOfMyTestClass.MyString: {0} ", this.ObjectOfMyTestClass.MyString);
Console.WriteLine("Read End");
Console.WriteLine("\n");
}
}
/// <summary>
/// The do write work.
/// </summary>
public void DoWriteWork()
{
// ThreadId where reading is done
var threadId = System.Threading.Thread.CurrentThread.ManagedThreadId;
// random number generator
var r = new Random();
var count = r.Next(15);
// new value for Int property
var tempInt = r.Next(5000);
this.ObjectOfMyTestClass.MyInitval = tempInt;
// new value for string Property
var tempString = "Randome" + r.Next(500).ToString(CultureInfo.InvariantCulture);
this.ObjectOfMyTestClass.MyString = tempString;
// printing on screen
lock (this.SyncObject)
{
Console.SetBufferSize(290, 290);
Console.SetCursorPosition(125, 25);
Console.WriteLine("\n");
Console.WriteLine("Write Start");
Console.WriteLine("Write => Thread Id: {0} ", threadId);
Console.WriteLine("Write => this.objectOfMyTestClass.MyInitval: {0} and New Value :{1} ", this.ObjectOfMyTestClass.MyInitval, tempInt);
Console.WriteLine("Write => this.objectOfMyTestClass.MyString: {0} and New Value :{1} ", this.ObjectOfMyTestClass.MyString, tempString);
Console.WriteLine("Write End");
Console.WriteLine("\n");
}
}
}
but still it will allow you to change property like array ,list . but if you apply more login in that then it may work for all type of property and field
C ++ movable 유형에서 영감을 얻은 이와 같은 작업을 수행합니다. 동결 / 해동 후 개체에 액세스하지 않는 것을 잊지 마십시오.
Of course, you can add a _data != null
check/throw if you want to be clear about why the user gets an NRE if accessing after thaw/freeze.
public class Data
{
public string _foo;
public int _bar;
}
public class Mutable
{
private Data _data = new Data();
public Mutable() {}
public string Foo { get => _data._foo; set => _data._foo = value; }
public int Bar { get => _data._bar; set => _data._bar = value; }
public Frozen Freeze()
{
var f = new Frozen(_data);
_data = null;
return f;
}
}
public class Frozen
{
private Data _data;
public Frozen(Data data) => _data = data;
public string Foo => _data._foo;
public int Bar => _data._bar;
public Mutable Thaw()
{
var m = new Mutable(_data);
_data = null;
return m;
}
}
참고URL : https://stackoverflow.com/questions/13691612/how-to-freeze-a-popsicle-in-net-make-a-class-immutable
'IT Share you' 카테고리의 다른 글
Github 창 : 커밋 실패 : 새 커밋을 생성하지 못했습니다. (0) | 2020.11.29 |
---|---|
RSA 키를 authorized_keys 파일에 추가하는 방법은 무엇입니까? (0) | 2020.11.29 |
Herb Sutter의 CppCon 2014 talk (Back to Basics : Modern C ++ Style)에서 값을 취하는 setter 멤버 함수가 권장되지 않는 이유는 무엇입니까? (0) | 2020.11.28 |
Windows에서 Python의 "easy_install"을 사용하는 방법… 쉽지 않습니다. (0) | 2020.11.28 |
javascript ArrayBuffer, 용도는 무엇입니까? (0) | 2020.11.28 |