C\#ConcurrentBag 의 실현 원 리 를 상세히 설명 하 다.

22422 단어 C#ConcurrentBag
1.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_tailListm_headListThreadLocal에 저 장 된 것 이 아니 라 모든 스 레 드 를 공유 하 는 것 입 니 다.

이상 은 C\#ConcurrentBag 의 실현 원 리 를 상세 하 게 설명 하 는 상세 한 내용 입 니 다.C\#ConcurrentBag 에 관 한 자 료 는 다른 관련 글 을 주목 하 세 요!

좋은 웹페이지 즐겨찾기