C\#ConcurrentBag 의 실현 원 리 를 상세히 설명 하 다.
22422 단어 C#ConcurrentBag
ConcurrentBag<T>
대외 적 으로 제공 하 는 방법 은List<T>
만큼 많 지 않 지만Enumerable
이 실현 하 는 확장 방법 도 있다.클래스 자체 가 제공 하 는 방법 은 다음 과 같다.명칭.
설명 하 다.
Add
대상 을 ConcurrentBag 에 추가 하기 맞다
CopyTo
지정 한 배열 색인 부터 ConcurrentBag 을 원 소 를 기 존의 1 차원 으로 복사 합 니 다. Array 맞다
Equals(Object)
지정 한 것 을 확정 하 다 Object 현재 Object。 (잇다 Object。)
Finalize
대상 이'쓰레기 회수'를 회수 하기 전에 자원 을 방출 하고 다른 청소 작업 을 수행 하도록 허용 합 니 다.(잇다 Object。)
GetEnumerator
반복 해서 방문 하기 ConcurrentBag 매 거 진
GetHashCode
특정 유형의 해시 함수 로 사용 합 니 다.(잇다 Object。)
GetType
현재 인 스 턴 스 가 져 오기 Type。 (잇다 Object。)
MemberwiseClone
현재 만 들 기 Object 의 얕 은 표 던 전 입 니 다.(잇다 Object。)
ToArray
ConcurrentBag 을 요 소 를 새 배열 로 복사 합 니 다.
ToString
현재 대상 을 나타 내 는 문자열 을 되 돌려 줍 니 다.(잇다 Object。)
TryPeek
ConcurrentBag 에서 대상 을 되 돌려 주지 만 대상 을 제거 하지 않 습 니 다.
TryTake
ConcurrentBag 에서 대상 을 제거 하고 되 돌려 줍 니 다.
2.ConcurrentBag 스 레 드 안전 실현 원리
2.1 ConcurrentBag 의 개인 필드
ConcurrentBag
스 레 드 안전 실현 은 주로 데이터 로 저 장 된 구조 와 미세 입자 의 잠 금 이다.
public class ConcurrentBag<T> : IProducerConsumerCollection<T>, IReadOnlyCollection<T>
{
// ThreadLocalList
ThreadLocal<ThreadLocalList> m_locals;
// ,
//
volatile ThreadLocalList m_headList, m_tailList;
//
// GlobalListsLock
bool m_needSync;
}
우선,우 리 는 그것 이 성명 한 개인 필드 를 볼 것 입 니 다.그 중에서 주의해 야 할 것 은 집합 한 데 이 터 는ThreadLocal
스 레 드 로 컬 저장 소 에 저 장 된 것 입 니 다.즉,모든 스 레 드 에 접근 하면 자신의 집합 데이터 목록 을 유지 하고 하나의 집합 에 있 는 데 이 터 는 서로 다른 스 레 드 의 로 컬 저장 공간 에 저장 할 수 있 기 때문에 스 레 드 가 자신의 로 컬 저장 대상 에 접근 하면 문제 가 없다.이것 이 바로 실제 스 레 드 안전 의 1 층 이 고 스 레 드 로 컬 저장 데 이 터 를 사용 하 는 것 이다.그 다음 에
ThreadLocalList m_headList, m_tailList;
이것 은 로 컬 목록 대상 을 저장 하 는 머리핀 과 꼬리 지침 입 니 다.이 두 개의 지침 을 통 해 우 리 는 옮 겨 다 니 는 방식 으로 모든 로 컬 목록 을 방문 할 수 있 습 니 다.이 는volatile
수식 을 사용 하여 로 컬 캐 시 를 허용 하지 않 습 니 다.모든 스 레 드 의 읽 기와 쓰 기 는 공유 메모리 에서 직접 작 동 합 니 다.이 는 변수 가 항상 일치 하 는 것 을 보장 합 니 다.모든 스 레 드 가 읽 기와 쓰기 작업 을 할 때 최신 값 입 니 다.마지막 으로 하나의 표 지 를 정 의 했 습 니 다.이 표 지 는 작업 스 레 드 가 반드시 동기 화 작업 을 해 야 한 다 는 것 을 알 립 니 다.이것 은 작은 입자 의 자 물 쇠 를 실현 하 는 것 입 니 다.몇 가지 조건 이 만족 하 는 상황 에서 만 스 레 드 동기 화 를 해 야 하기 때 문 입 니 다.
2.2 데이터 저장 에 사용 되 는 ThreadLocalList 클래스
다음은
ThreadLocalList
류 의 구 조 를 살 펴 보 자.이 종 류 는 실제 데 이 터 를 저장 한 위치 이다.실제로 그것 은 양 방향 링크 라 는 구 조 를 이용 하여 데이터 저장 을 한다.
[Serializable]
//
internal class Node
{
public Node(T value)
{
m_value = value;
}
public readonly T m_value;
public Node m_next;
public Node m_prev;
}
/// <summary>
///
/// </summary>
internal enum ListOperation
{
None,
Add,
Take
};
/// <summary>
///
/// </summary>
internal class ThreadLocalList
{
// null
internal volatile Node m_head;
//
private volatile Node m_tail;
// List
// ListOperation
internal volatile int m_currentOp;
//
private int m_count;
// The stealing count
// Node
internal int m_stealCount;
//
internal volatile ThreadLocalList m_nextList;
//
internal bool m_lockTaken;
// The owner thread for this list
internal Thread m_ownerThread;
// ,
internal volatile int m_version;
/// <summary>
/// ThreadLocalList
/// </summary>
/// <param name="ownerThread"> </param>
internal ThreadLocalList(Thread ownerThread)
{
m_ownerThread = ownerThread;
}
/// <summary>
/// item
/// </summary>
/// <param name="item">The item to add.</param>
/// <param name="updateCount"> .</param>
internal void Add(T item, bool updateCount)
{
checked
{
m_count++;
}
Node node = new Node(item);
if (m_head == null)
{
Debug.Assert(m_tail == null);
m_head = node;
m_tail = node;
m_version++; // ,
}
else
{
//
node.m_next = m_head;
m_head.m_prev = node;
m_head = node;
}
if (updateCount) //
{
m_count = m_count - m_stealCount;
m_stealCount = 0;
}
}
/// <summary>
/// item
/// </summary>
/// <param name="result">The removed item</param>
internal void Remove(out T result)
{
//
Debug.Assert(m_head != null);
Node head = m_head;
m_head = m_head.m_next;
if (m_head != null)
{
m_head.m_prev = null;
}
else
{
m_tail = null;
}
m_count--;
result = head.m_value;
}
/// <summary>
///
/// </summary>
/// <param name="result">the peeked item</param>
/// <returns>True if succeeded, false otherwise</returns>
internal bool Peek(out T result)
{
Node head = m_head;
if (head != null)
{
result = head.m_value;
return true;
}
result = default(T);
return false;
}
/// <summary>
/// item
/// </summary>
/// <param name="result">the removed item</param>
/// <param name="remove">remove or peek flag</param>
internal void Steal(out T result, bool remove)
{
Node tail = m_tail;
Debug.Assert(tail != null);
if (remove) // Take operation
{
m_tail = m_tail.m_prev;
if (m_tail != null)
{
m_tail.m_next = null;
}
else
{
m_head = null;
}
// Increment the steal count
m_stealCount++;
}
result = tail.m_value;
}
/// <summary>
/// , , ,
/// </summary>
internal int Count
{
get
{
return m_count - m_stealCount;
}
}
}
위의 코드 에서 우 리 는 이전의 관점 을 더욱 검증 할 수 있다.바로ConcurentBag<T>
한 스 레 드 에 데 이 터 를 저장 할 때 양 방향 링크 를 사용 하고ThreadLocalList
링크 에 대한 추가 삭제 와 검 사 를 실현 하 는 방법 이다.2.3、ConcurrentBag 은 새로운 요 소 를 실현 합 니 다.
다음은
ConcurentBag<T>
어떻게 원 소 를 새로 추 가 했 는 지 살 펴 보 자.
/// <summary>
/// , ,
///
/// </summary>
/// <returns></returns>
private ThreadLocalList GetUnownedList()
{
//
Contract.Assert(Monitor.IsEntered(GlobalListsLock));
//
//
ThreadLocalList currentList = m_headList;
while (currentList != null)
{
if (currentList.m_ownerThread.ThreadState == System.Threading.ThreadState.Stopped)
{
currentList.m_ownerThread = Thread.CurrentThread; // the caller should acquire a lock to make this line thread safe
return currentList;
}
currentList = currentList.m_nextList;
}
return null;
}
/// <summary>
/// ,
/// </summary>
/// <param name="forceCreate"> , </param>
/// <returns>The local list object</returns>
private ThreadLocalList GetThreadList(bool forceCreate)
{
ThreadLocalList list = m_locals.Value;
if (list != null)
{
return list;
}
else if (forceCreate)
{
// m_tailList
lock (GlobalListsLock)
{
// ,
//
if (m_headList == null)
{
list = new ThreadLocalList(Thread.CurrentThread);
m_headList = list;
m_tailList = list;
}
else
{
// ConcurrentBag
//
//
list = GetUnownedList();
//
if (list == null)
{
list = new ThreadLocalList(Thread.CurrentThread);
m_tailList.m_nextList = list;
m_tailList = list;
}
}
m_locals.Value = list;
}
}
else
{
return null;
}
Debug.Assert(list != null);
return list;
}
/// <summary>
/// Adds an object to the <see cref="ConcurrentBag{T}"/>.
/// </summary>
/// <param name="item">The object to be added to the
/// <see cref="ConcurrentBag{T}"/>. The value can be a null reference
/// (Nothing in Visual Basic) for reference types.</param>
public void Add(T item)
{
// , , ( add)
ThreadLocalList list = GetThreadList(true);
// AddInternal
AddInternal(list, item);
}
/// <summary>
/// </summary>
/// <param name="list"></param>
/// <param name="item"></param>
private void AddInternal(ThreadLocalList list, T item)
{
bool lockTaken = false;
try
{
#pragma warning disable 0420
Interlocked.Exchange(ref list.m_currentOp, (int)ListOperation.Add);
#pragma warning restore 0420
// :
// ,
// m_needSync,
if (list.Count < 2 || m_needSync)
{
// None
list.m_currentOp = (int)ListOperation.None;
//
Monitor.Enter(list, ref lockTaken);
}
// ThreadLocalList.Add
// Count
list.Add(item, lockTaken);
}
finally
{
list.m_currentOp = (int)ListOperation.None;
if (lockTaken)
{
Monitor.Exit(list);
}
}
}
위의 코드 에서 우 리 는Add()
방법 이 어떻게 작 동 하 는 지 잘 알 수 있 습 니 다.그 중에서 관건 은GetThreadList()
방법 입 니 다.이 방법 을 통 해 현재 스 레 드 의 데이터 저장 목록 대상 을 얻 을 수 있 습 니 다.데이터 저장 목록 이 존재 하지 않 으 면 자동 으로 만 들 거나GetUnownedList()
방법 으로 정지 되 었 지만 데이터 목록 이 저 장 된 스 레 드 를 찾 을 수 있 습 니 다.그리고 데이터 목록 을 현재 스 레 드 에 되 돌려 메모리 누 출 을 방지 합 니 다.데 이 터 를 추가 하 는 과정 에서 초 미세 먼지
lock
동기 잠 금 이 이 뤄 져 성능 이 높 을 것 으로 보인다.삭제 와 다른 조작 은 추가 와 유사 하 므 로 본 고 는 더 이상 군말 하지 않 습 니 다.2.4.ConcurrentBag 는 어떻게 교체 기 모델 을 실현 합 니까?
위의 코드 를 보고 나 서 나 는
ConcurrentBag<T>
어떻게IEnumerator
교체 방문 을 실현 하 는 지 궁금 했다.ConcurrentBag<T>
는 서로 다른 스 레 드 에 분 산 된ThreadLocalList
을 통 해 데 이 터 를 저장 하 는 것 이기 때문에 교체 기 모드 를 실현 할 때 과정 이 비교적 복잡 할 것 이다.뒤에 원본 코드 를 살 펴 본 결과
ConcurrentBag<T>
교체 기 모드 를 실현 하기 위해 서로 다른 스 레 드 에 분 리 된 데 이 터 를 모두 하나의List<T>
집합 에 저장 한 다음 이 던 전의 교체 기 를 되 돌려 주 었 다.그래서 매번 교체 기 를 방문 할 때마다List<T>
사본 을 새로 만 듭 니 다.이렇게 하면 일정한 저장 공간 을 낭비 하지만 논리 적 으로 더욱 간단 합 니 다.
/// <summary>
///
/// </summary>
private void ReleaseAllLocks()
{
//
// ThreadLocalList
ThreadLocalList currentList = m_headList;
while (currentList != null)
{
if (currentList.m_lockTaken)
{
currentList.m_lockTaken = false;
Monitor.Exit(currentList);
}
currentList = currentList.m_nextList;
}
}
/// <summary>
///
/// </summary>
/// <param name="lockTaken">The lock taken result from the Freeze method</param>
private void UnfreezeBag(bool lockTaken)
{
//
//
ReleaseAllLocks();
m_needSync = false;
if (lockTaken)
{
Monitor.Exit(GlobalListsLock);
}
}
/// <summary>
///
/// </summary>
private void WaitAllOperations()
{
Contract.Assert(Monitor.IsEntered(GlobalListsLock));
ThreadLocalList currentList = m_headList;
//
while (currentList != null)
{
if (currentList.m_currentOp != (int)ListOperation.None)
{
SpinWait spinner = new SpinWait();
// , cuurentOp
while (currentList.m_currentOp != (int)ListOperation.None)
{
spinner.SpinOnce();
}
}
currentList = currentList.m_nextList;
}
}
/// <summary>
///
/// </summary>
private void AcquireAllLocks()
{
Contract.Assert(Monitor.IsEntered(GlobalListsLock));
bool lockTaken = false;
ThreadLocalList currentList = m_headList;
// ThreadLocalList ThreadLocalList
while (currentList != null)
{
// / bllock
try
{
Monitor.Enter(currentList, ref lockTaken);
}
finally
{
if (lockTaken)
{
currentList.m_lockTaken = true;
lockTaken = false;
}
}
currentList = currentList.m_nextList;
}
}
/// <summary>
/// Local helper method to freeze all bag operations, it
/// 1- Acquire the global lock to prevent any other thread to freeze the bag, and also new new thread can be added
/// to the dictionary
/// 2- Then Acquire all local lists locks to prevent steal and synchronized operations
/// 3- Wait for all un-synchronized operations to be done
/// </summary>
/// <param name="lockTaken">Retrieve the lock taken result for the global lock, to be passed to Unfreeze method</param>
private void FreezeBag(ref bool lockTaken)
{
Contract.Assert(!Monitor.IsEntered(GlobalListsLock));
// m_needSync
Monitor.Enter(GlobalListsLock, ref lockTaken);
// /
m_needSync = true;
//
AcquireAllLocks();
//
WaitAllOperations();
}
/// <summary>
/// , CopyTo ToArray 。
/// , /
/// Freeze/UnFreeze
/// </summary>
/// <returns>List the contains the bag items</returns>
private List<T> ToList()
{
Contract.Assert(Monitor.IsEntered(GlobalListsLock));
// List
List<T> list = new List<T>();
ThreadLocalList currentList = m_headList;
// ThreadLocalList Node list
while (currentList != null)
{
Node currentNode = currentList.m_head;
while (currentNode != null)
{
list.Add(currentNode.m_value);
currentNode = currentNode.m_next;
}
currentList = currentList.m_nextList;
}
return list;
}
/// <summary>
/// Returns an enumerator that iterates through the <see
/// cref="ConcurrentBag{T}"/>.
/// </summary>
/// <returns>An enumerator for the contents of the <see
/// cref="ConcurrentBag{T}"/>.</returns>
/// <remarks>
/// The enumeration represents a moment-in-time snapshot of the contents
/// of the bag. It does not reflect any updates to the collection after
/// <see cref="GetEnumerator"/> was called. The enumerator is safe to use
/// concurrently with reads from and writes to the bag.
/// </remarks>
public IEnumerator<T> GetEnumerator()
{
// Short path if the bag is empty
if (m_headList == null)
return new List<T>().GetEnumerator(); // empty list
bool lockTaken = false;
try
{
// ConcurrentBag
FreezeBag(ref lockTaken);
// ToList List IEnumerator
return ToList().GetEnumerator();
}
finally
{
UnfreezeBag(lockTaken);
}
}
위의 코드 에서 알 수 있 듯 이 교체 기 대상 을 얻 기 위해 모두 세 단계 의 주요 작업 을 진행 했다.1.
FreezeBag()
방법 을 사용 하여 전체ConcurrentBag<T>
집합 을 동결 합 니 다.집합List<T>
던 전 을 생 성 해 야 하기 때문에 던 전 을 생 성 하 는 동안 다른 스 레 드 가 데 이 터 를 변경 할 수 없습니다.2.
ConcurrrentBag<T>
던 전 생 성List<T>
.ConcurrentBag<T>
데 이 터 를 저장 하 는 방식 이 비교적 특수 하기 때문에 교체 기 모델 을 직접 실현 하 는 것 이 어렵 고 스 레 드 안전 과 논 리 를 고려 하여 가장 좋 은 방법 은 사본 을 만 드 는 것 이다.3.상기 조작 을 완성 한 후에
UnfreezeBag()
방법 으로 전체 집합 을 해동 할 수 있다.그렇다면
FreezeBag()
방법 은 어떻게 전체 집합 을 동결 합 니까?하 긴 세 걸음 으로 나 눠 서 가 는 거 야.1.먼저 전역 자 물 쇠 를 가 져 옵 니 다.
Monitor.Enter(GlobalListsLock, ref lockTaken);
라 는 문 구 를 통 해 다른 스 레 드 는 집합 을 동결 할 수 없습니다.2.그리고 모든 스 레 드
ThreadLocalList
의 자 물 쇠 를 가 져 옵 니 다.'AcquireAllLocks()방법 으로 옮 겨 다 니 며 가 져 옵 니 다.이렇게 하면 다른 스 레 드 는 그것 을 조작 하여 데 이 터 를 손상 시 킬 수 없다.3.이미 작업 프로 세 스 스 레 드 가 끝 날 때 까지 기 다 립 니 다.
WaitAllOperations()
방법 으로 이 루어 집 니 다.이 방법 은 모든ThreadLocalList
대상 의m_currentOp
속성 을 옮 겨 다 니 며 모든None
작업 을 확보 합 니 다.이상 의 절 차 를 마 친 후에 진정 으로 전체
ConcurrentBag<T>
집합 을 동결 한 것 이 고 해동 하려 면 비슷 하 다.더 이상 군말 하지 않 겠 습 니 다.3.총화
다음 그림 은
ConcurrentBag<T>
데 이 터 를 어떻게 저장 하 는 지 설명 한다.모든 스 레 드 중의ThreadLocal
를 통 해 스 레 드 로 컬 저장 을 실현 하고 모든 스 레 드 에 이런 구조 가 있 으 며 서로 간섭 하지 않 습 니 다.그리고 각 라인 의m_headList
은 항상ConcurrentBag<T>
의 첫 번 째 목록 을 가리 키 고m_tailList
는 마지막 목록 을 가리킨다.목록 과 목록 사 이 는m_locals
아래m_nextList
를 통 해 연결 되 어 하나의 단일 체인 표를 구성한다.데 이 터 는 각 스 레 드
m_locals
에 저장 되 고Node
류 를 통 해 양 방향 링크 를 구성한다.PS:주의
m_tailList
와m_headList
는ThreadLocal
에 저 장 된 것 이 아니 라 모든 스 레 드 를 공유 하 는 것 입 니 다.이상 은 C\#ConcurrentBag 의 실현 원 리 를 상세 하 게 설명 하 는 상세 한 내용 입 니 다.C\#ConcurrentBag 에 관 한 자 료 는 다른 관련 글 을 주목 하 세 요!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
WebView2를 Visual Studio 2017 Express에서 사용할 수 있을 때까지Evergreen .Net Framework SDK 4.8 VisualStudio2017에서 NuGet을 사용하기 때문에 패키지 관리 방법을 packages.config 대신 PackageReference를 사용해야...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.