스레드 안전을 목표로하는 클래스를 구현할 때 내부 구조가 초기화되기 전에 초기화가 완료되었는지 확인하기 위해 생성자 끝에 메모리 장벽을 포함해야합니까? 아니면 다른 스레드에서 인스턴스를 사용할 수있게 만들기 전에 메모리 장벽을 삽입하는 것은 소비자의 책임입니까?스레드 안전 클래스는 생성자 끝에 메모리 장벽이 있어야합니까?
간체 질문 :
그 때문에 초기화 및 스레드 안전 클래스의 액세스와 메모리 장벽의 부족으로 잘못된 행동을 줄 수 아래의 코드에서 경주의 위험이 있습니까? 또는 스레드로부터 안전한 클래스 자체가이를 방지해야합니까?
ConcurrentQueue<int> queue = null;
Parallel.Invoke(
() => queue = new ConcurrentQueue<int>(),
() => queue?.Enqueue(5));
두 번째 대리자가 첫 번째 메시지보다 먼저 실행될 때와 같이 프로그램에서 아무 것도 대기열에 추가하지 않아도됩니다. (null 조건부 연산자 ?.
은 여기에서 NullReferenceException
에 대해 보호합니다.) 그러나 프로그램이 IndexOutOfRangeException
, NullReferenceException
, 대기열에 5
을 여러 번 던지거나 무한 루프에 걸리거나 다른 이상한 행동을해서는 안됩니다 내부 구조에 대한 인종적 위험에 의한 것.
정교 질문 :
구체적으로는, 내가 큐에 대한 간단한 스레드 안전 래퍼를 구현되어 있다고 가정 해. (I는 .NET 이미 ConcurrentQueue<T>
제공하는 알고 있어요, 이것은 단지 예입니다.) 내가 쓸 수 :
public class ThreadSafeQueue<T>
{
private readonly Queue<T> _queue;
public ThreadSafeQueue()
{
_queue = new Queue<T>();
// Thread.MemoryBarrier(); // Is this line required?
}
public void Enqueue(T item)
{
lock (_queue)
{
_queue.Enqueue(item);
}
}
public bool TryDequeue(out T item)
{
lock (_queue)
{
if (_queue.Count == 0)
{
item = default(T);
return false;
}
item = _queue.Dequeue();
return true;
}
}
}
이 구현은 thread 세이프 한 번 초기화됩니다. 그러나 초기화 자체가 다른 소비자 스레드에 의해 경쟁되면 경주 위험이 발생할 수 있으므로 후자 스레드가 내부 Queue<T>
이 초기화되기 전에 인스턴스에 액세스하게됩니다. 고안된 예 :
ThreadSafeQueue<int> queue = null;
Parallel.For(0, 10000, i =>
{
if (i == 0)
queue = new ThreadSafeQueue<int>();
else if (i % 2 == 0)
queue?.Enqueue(i);
else
{
int item = -1;
if (queue?.TryDequeue(out item) == true)
Console.WriteLine(item);
}
});
위의 코드는 일부 숫자가 누락 될 수 있습니다. 그러나 메모리 장벽이없는 경우 Enqueue
또는 TryDequeue
시간이 될 때까지 내부 Queue<T>
이 초기화되지 않아서 NullReferenceException
(또는 다른 별난 결과)이 표시 될 수 있습니다.
생성자 끝에 메모리 장벽을 포함시키는 것은 스레드 안전 클래스의 책임입니까, 아니면 클래스의 인스턴스화와 다른 스레드에 대한 가시성 사이에 메모리 장벽을 포함해야하는 소비자입니까? 스레드로부터 안전한 것으로 표시된 클래스에 대한 .NET Framework의 규칙은 무엇입니까?
편집 : 고급 스레드 주제이므로 일부 의견의 혼동을 이해합니다. 인스턴스는 적절한 동기화없이 다른 스레드에서 액세스하는 경우 하프 베이크로 나타납니다. 이 항목은 double-checked locking의 컨텍스트에서 광범위하게 논의되며, 이는 메모리 장벽을 사용하지 않고 ECMA CLI 사양 (예 : volatile
)없이 손상되었습니다. Jon Skeet 당 :
자바 메모리 모델은 새 개체에 대한 참조가 예를에 할당되기 전에 생성자가 완료되었는지 확인하지 않습니다. Java 메모리 모델은 버전 1에 대한 재 작업을 수행했습니다.5이지만, 이중 체크 락은 휘발성 변수 (C#에서와 같이 ) 없이도 여전히 깨지게됩니다.
메모리 장벽이 없어도 ECMA CLI 사양에서 오류가 발생했습니다. .NET 2.0 메모리 모델 (ECMA 사양보다 강력 함)에서는 안전 할 수 있지만, 특히 안전성에 대해 의심의 여지가있는 경우 해당 의미에 의존하지 않을 수 있습니다.
언급 한 'ConcurrentQueue'의 소스 코드는 생성자에 보호 기능이 없습니다. 당신이 원하는 것을 만드십시오. http://referencesource.microsoft.com/#mscorlib/system/Collections/Concurrent/ConcurrentQueue.cs,18bcbcbdddbcfdcb –
초기화를 스레드로부터 안전하게 만드는 Lazy을 사용하여 소비자를 초기화하는 방법은 어떻습니까? :) –
user3185569
실제로 비동기 호출을 생성자가 없다면, 참조가 인스턴스가 생성되기 전에 인스턴스를 참조하도록 설정할 수 있습니까? – Uueerdo