多线程下,不关心获取消息的严格顺序,可使用这个集合,我们在DotNetty中使用这个集合接受网络接受到的包,另外一边多个消费者再从这个集合获取包
// Licensed to the .NET Foundation under one or more agreements.
// 根据一项或多项协议授权给.Net基金会
// The .NET Foundation licenses this file to you under the MIT license.
// .Net基金会根据MIT协议将这个文件授权给你
// See the LICENSE file in the project root for more information.
// 更多信息请看项目的根目录下的LICENSE文件
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
namespace System.Collections.Concurrent
{
/// <summary>
/// Represents a thread-safe, unordered collection of objects.
/// 这个是一个线程安全的无序Bag集合类。
/// </summary>
/// <typeparam name="T">
/// Specifies the type of elements in the bag.
/// 代表这个Bag集合中元素的类型。
/// </typeparam>
/// <remarks>
/// <para>
/// Bags are useful for storing objects when ordering doesn't matter, and unlike sets, bags support
/// duplicates. <see cref="ConcurrentBag{T}"/> is a thread-safe bag implementation, optimized for
/// scenarios where the same thread will be both producing and consuming data stored in the bag.
/// 对于不关心存储顺序的需求来说,这个Bag类将会很有用,与Set(如HashSet、KinkedSet等)集合不同的是, 它支持复制。
/// 它的主要优化点在于为每个线程提供了单独的"添加"(Add())、"移除"(Take())方法,在这个里面也可以说成为每个线程提供了相对独立的bag,这样就保证了不同线程之间不会发生激烈的竞争,进而提高了吞吐量。
/// </para>
/// <para>
/// <see cref="ConcurrentBag{T}"/>
/// accepts null reference (Nothing in Visual Basic) as a valid value for reference types.
/// 可添加null引用作为有效值。
/// </para>
/// <para>
/// All public and protected members of <see cref="ConcurrentBag{T}"/> are thread-safe and may be used
/// concurrently from multiple threads.
/// Bag类的所有public和protected成员都可以在多线程中安全使用
/// </para>
/// </remarks>
/// 先介绍Bag实现的两个接口
/// 1.IProducerConsumerCollection : 主要提供类似生产消费的行为所需要的方法(大部分ConcurrentXXX类都实现了这个接口)
/// public interface IProducerConsumerCollection<T> : IEnumerable<T>, IEnumerable, ICollection { void CopyTo(T[] array, int index); bool TryAdd(T item); bool TryTake(out T item); T[] ToArray(); }
/// 2.IReadOnlyCollection :只提供访问当前元素数量的属性
/// public interface IReadOnlyCollection<out T> : IEnumerable<T>, IEnumerable { int Count { get; } }
[DebuggerTypeProxy(typeof(IProducerConsumerCollectionDebugView<>))]
[DebuggerDisplay("Count = {Count}")]
public class ConcurrentBag<T> : IProducerConsumerCollection<T>, IReadOnlyCollection<T>
{
/// <summary>
/// The per-bag, per-thread work-stealing queues.
/// ThreadLocal变量,在这个里面,每一个 WorkStealingQueue 实例 对应一个线程,当线程获取队列时,可以获取到对应的队列。
/// </summary>
private readonly ThreadLocal<WorkStealingQueue> _locals;
/// <summary>
/// The head work stealing queue in a linked list of queues.
/// 这个变量指向链表的头结点,及链表的第一个节点
/// </summary>
private volatile WorkStealingQueue _workStealingQueues;
/// <summary>
/// 空队列到非空队列发生的次数
/// Number of times any list transitions from empty to non-empty.
/// </summary>
private long _emptyToNonEmptyListTransitionCount;
/// <summary>
/// Initializes a new instance of the <see cref="ConcurrentBag{T}"/> class.
/// 初始化一个ConcurrentBag类
/// </summary>
public ConcurrentBag()
{
_locals = new ThreadLocal<WorkStealingQueue>();
}
/// <summary>
/// Initializes a new instance of the <see cref="ConcurrentBag{T}"/>
/// class that contains elements copied from the specified collection.
/// 初始化一个ConcurrentBag类,并且将collection中的元素全部拷贝到其中,
/// </summary>
/// <param name="collection">
/// The collection whose elements are copied to the new <see cref="ConcurrentBag{T}"/>.
/// 这个集合的元素将被拷贝到 新的ConcurrentBag中
/// </param>
/// <exception cref="ArgumentNullException"><paramref name="collection"/> is a null reference
/// (Nothing in Visual Basic).</exception>
public ConcurrentBag(IEnumerable<T> collection)
{
// 如果传入的集合为空将直接抛出异常
if (collection == null)
{
throw new ArgumentNullException(nameof(collection), SR.ConcurrentBag_Ctor_ArgumentNullException);
}
// 做ConcurrentBag()的事
_locals = new ThreadLocal<WorkStealingQueue>();
// 以当前线程创建一个队列
WorkStealingQueue queue = GetCurrentThreadWorkStealingQueue(forceCreate: true);
// 将collection中的数据放入队列中
foreach (T item in collection)
{
queue.LocalPush(item, ref _emptyToNonEmptyListTransitionCount);
}
}
/// <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.
/// 可以添加任何元素到集合中包括null
/// </param>
public void Add(T item)
{
// 获取当前线程绑定的队列, 具体查看GetCurrentThreadWorkStealingQueue()
WorkStealingQueue currentThreadWorkStealingQueue = GetCurrentThreadWorkStealingQueue(forceCreate: true);
// 将item放入当前线程对应的队列
currentThreadWorkStealingQueue.LocalPush(item, ref _emptyToNonEmptyListTransitionCount);
}
/// <summary>
/// Attempts to add an object to the <see cref="ConcurrentBag{T}"/>.
/// 尝试添加一个元素, 这里添加这个方法其实只是为了迎合IProducerConsumerCollection接口,统一接口,而从代码的实现来看不可能添加失败
/// </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>
/// 可以添加任何元素到集合中包括null
/// <returns>
/// Always returns true
/// 总是返回true
/// </returns>
bool IProducerConsumerCollection<T>.TryAdd(T item)
{
// 调用上面的Add()
Add(item);
// 直接返回true
// 为什么它可以直接返回true,其实看了开头说的,它的优化点是保证每个线程值操作自己对应的队列,相互之间互不干涉,就可以大概明白了
return true;
}
/// <summary>
/// Attempts to remove and return an object from the <see cref="ConcurrentBag{T}"/>.
/// 尝试移除并且返回这个被移除的元素
/// </summary>
/// <param name="result">When this method returns, <paramref name="result"/> contains the object
/// removed from the <see cref="ConcurrentBag{T}"/> or the default value
/// of <typeparamref name="T"/> if the operation failed.
/// 返回true,result指向被移除的元素; 或则返回false, result指向元素类型的默认值(如一般类的默认值为null)
/// </param>
/// <returns>
/// true if an object was removed successfully; otherwise, false.
/// 移除成功返回true,否则返回false
/// </returns>
public bool TryTake(out T result)
{
WorkStealingQueue queue = null;
if (null != (queue = GetCurrentThreadWorkStealingQueue(forceCreate: false)))
{
// 获取当前线程绑定的队列, 具体查看GetCurrentThreadWorkStealingQueue()
// 如果已经创建当前线程对应的队列
return queue.TryLocalPop(out result);
}
else
{
// 如果当前线程没有创建对应的队列
// 则去遍历所有其它线程对应的队列,还记得前面的_workStealingQueues,通过它就可以
return TrySteal(out result, take: true);
}
}
/// <summary>
/// Attempts to return an object from the <see cref="ConcurrentBag{T}"/> without removing it.
/// 尝试返回一个元素,但不移除(这是它和TryTake()的区别)
/// </summary>
/// <param name="result">When this method returns, <paramref name="result"/> contains an object from
/// the <see cref="ConcurrentBag{T}"/> or the default value of
/// <typeparamref name="T"/> if the operation failed.</param>
/// <returns>true if and object was returned successfully; otherwise, false.</returns>
public bool TryPeek(out T result)
{
// 大致的逻辑与TryTake()类似
WorkStealingQueue queue = GetCurrentThreadWorkStealingQueue(forceCreate: false);
// 唯一注意有两点:
// 1.这里只是检查是否存在,所以使用TryLocalPeek()
// 2.TrySteal()的第二个参数take: 为false,意味着不会移除与那苏
return (queue != null && queue.TryLocalPeek(out result)) || TrySteal(out result, take: false);
}
/// <summary>
/// Gets the work-stealing queue data structure for the current thread.
/// 获取当前线程对应的队列
/// </summary>
/// <param name="forceCreate">
/// Whether to create a new queue if this thread doesn't have one.
/// 是否在当前线程不存在对应队列的情况下创建队列
/// </param>
/// <returns>
/// The local queue object, or null if the thread doesn't have one.
/// 返回当前线程对应的队列,或则在forceCreate为false的情况下返回null
/// </returns>
private WorkStealingQueue GetCurrentThreadWorkStealingQueue(bool forceCreate)
{
// 如果当前线程存在一个被创建的队列,则直接返回
WorkStealingQueue value = null;
if ((value = _locals.Value) != null)
{
return value;
}
if (forceCreate)
// 如果不存在,且可以被创建,则直接返回新创建的队列
return CreateWorkStealingQueueForCurrentThread();
else
// 不允许被创建,则直接返回null
return null;
}
/// <summary>
/// 创建当前线程对应的WorkStealingQueue队列
/// </summary>
/// <returns>
/// 返回一个当前线程对应的队列
/// </returns>
private WorkStealingQueue CreateWorkStealingQueueForCurrentThread()
{
// necessary to update _workStealingQueues, so as to synchronize with freezing operations
// 需要更新_workStealingQueues,而这里可能被多个线程同时调用,所以需要加锁
lock (GlobalQueuesLock)
{
// 头节点队列
WorkStealingQueue head = _workStealingQueues;
WorkStealingQueue queue = null;
if (head != null)
// 如果头节点已经被创建,则先去尝试寻找是否当前线程对应的队列,未找到则queue被赋值为null
// 为什么怎么做?具体原因查看GetUnownedWorkStealingQueue()
queue = GetUnownedWorkStealingQueue();
// 创建当前线程对应的队列,并赋值给头节点
if (queue == null)
{
_workStealingQueues = queue = new WorkStealingQueue(head);
}
// 将创建的队列放到ThreadLocal中,方便下一次直接获取
_locals.Value = queue;
return queue;
}
}
/// <summary>
/// Try to reuse an unowned queue. If a thread interacts with the bag and then exits,
/// the bag purposefully retains its queue, as it contains data associated with the bag.
/// 尝试去重新使用当前链表中没有对应线程的队列且该队列之前对应的线程为当前线程,为什么会有这样的队列?
/// 我稍微解释一下,存在这样的情况:
/// 当线程操作(如Add()、Take())过ConcurrentBag,“后来离开了(这里指的是线程结束)”, 但线程a对应的队列还存在ConcurrentBag中,
/// 由于所有被创建的队列是在一个链表中,所以还是可以访问到的,没有被移除,这里就是要检测这种情况。
///
/// 那有个问题就是既然线程结束了,为什么会有相同线程id的线程再次出现那?原因就是线程的id可能被复用,下面有官方对于这个调用的解释。
/// Look for a thread that has the same ID as this one.
/// 寻找一个队列,且该队列保存的线程id为当前线程的id(听起来有点搞笑o_o),
/// It won't have come from the same thread, but if our thread ID is reused,
/// 要知道,即使存在这样的队列,那当时创建它的线程也不可能时当前线程,只有在线程id被复用时才可能会出现这种情况,
/// we know that no other thread can have the same ID and thus no other thread can be using this queue.
/// 但是我们知道任何线程(这里不考虑已经停止的线程)不可能用相同的线程id,因此不会有线程正在使用这个队列。
///
/// 小结:
/// 所以如果你可以保证操作ConcurrentBag的线程是唯一的,并且不会动态扩增或缩小,其实是不需要调用函数的,
/// 而对于不需要的情况,除去这个调用可以减少一次链表的遍历O(n),对于提高性能还是相当可观的(尤其在服务器启动的前期,面临大量访问时),
/// 但是,由于这一步只在线程对应的队列不存在的情况下才会调用,所以在所有线程都拥有对应的队列时,这也就无所谓了
/// </summary>
/// <returns>
/// The queue object, or null if no unowned queue could be gathered.
/// 返回之前线程对应的队列,否则返回null
/// </returns>
private WorkStealingQueue GetUnownedWorkStealingQueue()
{
// Debug断言 主要判断当前 GlobalQueuesLock 是否被上锁
Debug.Assert(Monitor.IsEntered(GlobalQueuesLock));
// 获取当前线程id
int currentThreadId = Environment.CurrentManagedThreadId;
// 模拟单链表遍历,逐个遍历元素: head -> next -> ... -> next -> tail
// for(init; condition; move)
// WorkStealingQueue queue = _workStealingQueues : 获取头节点
// queue != null : 继续循环条件
// queue = queue._nextQueue : 移动向下一个队列
for (WorkStealingQueue queue = _workStealingQueues; queue != null; queue = queue._nextQueue)
{
// 如果当前线程的id等于已经存在队列对应的id则直接返回该队列
if (queue._ownerThreadId == currentThreadId)
{
return queue;
}
}
return null;
}
/// <summary>
/// Local helper method to steal an item from any other non empty thread.
/// 本地辅助方法,主要去其它的线程的非空队列中 查看/删除 一个元素
/// </summary>
/// <param name="result">
/// To receive the item retrieved from the bag
/// 将获取的元素赋值到result上面,out修饰表明它需要在函数内部被赋值,类似数据库的函数的存储过程的参数类型
/// </param>
/// <param name="take">
/// Whether to remove or peek.
/// true: 在队列中删除元素
/// false: 仅仅返回队列中的元素
/// </param>
/// <returns>
/// True if succeeded, false otherwise.
/// 找到一个元素则返回 true,否则返回false
/// </returns>
private bool TrySteal(out T result, bool take)
{
// 系统自带log
if (take)
{
CDSCollectionETWBCLProvider.Log.ConcurrentBag_TryTakeSteals();
}
else
{
CDSCollectionETWBCLProvider.Log.ConcurrentBag_TryPeekSteals();
}
while (true)
{
// TODO
// We need to track whether any lists transition from empty to non-empty both before
// and after we attempt the steal in case we don't get an item:
// 我们需要去追踪是否有 集合 发生 从 没有数据 到 有数据 的转变,不管这个转变发生在 调用TrySteal()前还是后, 这个可以防止我们错过获取这些加入的数据:
// If we don't get an item, we need to handle the possibility of a race condition that led to
// an item being added to a list after we already looked at it in a way that breaks linearizability.
// 如果我们没有得到这个加入的元素,我们需要去处理可能出现的竞赛条件,如果我们已经已经遍历过的队列被加入元素,这就破坏了线性化的方式。
// For example, say there are three threads 0, 1, and 2, each with their own
// 例如这里,有三个线程它们各自拥有自己的队列
// list that's currently empty. We could then have the following series of operations:
// - Thread 2 adds an item, such that there's now 1 item in the bag.
// - Thread 1 sees that the count is 1 and does a Take. Its local list is empty, so it tries to
// steal from list 0, but it's empty. Before it can steal from Thread 2, it's pre-empted.
// - Thread 0 adds an item. The count is now 2.
// - Thread 2 takes an item, which comes from its local queue. The count is now 1.
// - Thread 1 continues to try to steal from 2, finds it's empty, and fails its take, even though
// at any given time during its take the count was >= 1. Oops.
// This is particularly problematic for wrapper types that track count using their own synchronization,
// e.g. BlockingCollection, and thus expect that a take will always be successful if the number of items
// is known to be > 0.
//
// We work around this by looking at the number of times any list transitions from == 0 to > 0,
// checking that before and after the steal attempts. We don't care about > 0 to > 0 transitions,
// because a steal from a list with > 0 elements would have been successful.
// 原子读取_emptyToNonEmptyListTransitionCount的值
long initialEmptyToNonEmptyCounts = Interlocked.Read(ref _emptyToNonEmptyListTransitionCount);
// If there's no local queue for this thread, just start from the head queue
// and try to steal from each queue until we get a result. If there is a local queue from this thread,
// then start from the next queue after it, and then iterate around back from the head to this queue,
// not including it.
// 当前线程队列
WorkStealingQueue localQueue = GetCurrentThreadWorkStealingQueue(forceCreate: false);
bool gotItem;
if (localQueue == null)
// 如果当前线程不存在对应的队列,则遍历所有已经存在的队列
gotItem = TryStealFromTo(_workStealingQueues, null, out result, take);
else
// 如果当前线程存在对应的队列
// 先在localQueue(当前队列对应的节点)到链表的尾部中寻找元素;
// 如果没有找到元素,则从链表的头节点开始到localQueue的前一个节点中寻找元素
gotItem = TryStealFromTo(localQueue._nextQueue, null, out result, take) ||
TryStealFromTo(_workStealingQueues, localQueue, out result, take);
// 如果找到则直接返回true
if (gotItem)
{
return true;
}
// 如果转换次数在两次读取之间没有变化,直接返回
if (Interlocked.Read(ref _emptyToNonEmptyListTransitionCount) == initialEmptyToNonEmptyCounts)
{
// The version number matched, so we didn't get an item and we're confident enough
// in our steal attempt to say so.
return false;
}
// Some list transitioned from empty to non-empty between just before the steal and now.
//
// Since we don't know if it caused a race condition like the above description, we
// have little choice but to try to steal again.
// 当前发生空队列到非空队列的转换, 继续循环查找
}
}
/// <summary>
/// Attempts to steal from each queue starting from <paramref name="startInclusive"/> to <paramref name="endExclusive"/>.
/// 尝试从其它线程对应的队列中获取(偷取)一个元素, 遍历其它线程队列的范围为从startInclusive到endExclusive的上一个节点结束
/// 同样获取到则将result赋值为元素值,并返回true,否则设置为元素的默认值并返回false
/// </summary>
private bool TryStealFromTo(WorkStealingQueue startInclusive, WorkStealingQueue endExclusive, out T result,
bool take)
{
// 单链表遍历
for (WorkStealingQueue queue = startInclusive; queue != endExclusive; queue = queue._nextQueue)
{
// 找到一个元素直接返回
if (queue.TrySteal(out result, take))
{
return true;
}
}
// 将result赋值为T的默认值
result = default(T);
return false;
}
/// <summary>
/// </summary>
/// <param name="array">The one-dimensional <see cref="T:System.Array">Array</see> that is the
/// destination of the elements copied from the
/// <see cref="ConcurrentBag{T}"/>. The <see cref="T:System.Array">Array</see> must have zero-based indexing.</param>
/// <param name="index">The zero-based index in <paramref name="array"/> at which copying begins.</param>
/// <exception cref="ArgumentNullException"><paramref name="array"/> is a null reference (Nothing in
/// Visual Basic).</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than
/// zero.</exception>
/// <exception cref="ArgumentException"><paramref name="index"/> is equal to or greater than the
/// length of the <paramref name="array"/>
/// -or- the number of elements in the source <see
/// cref="ConcurrentBag{T}"/> is greater than the available space from
/// <paramref name="index"/> to the end of the destination <paramref name="array"/>.</exception>
///
/// <summary>
/// Copies the <see cref="ConcurrentBag{T}"/> elements to an existing
/// one-dimensional <see cref="T:System.Array">Array</see>, starting at the specified array index.
/// 复制所有队列中的元素到array一维数组中
/// </summary>
/// <param name="array">元素需要被复制到的地方</param>
/// <param name="index"></param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="InvalidCastException"></exception>
public void CopyTo(T[] array, int index)
{
// 处理异常
if (array == null)
{
throw new ArgumentNullException(nameof(array), SR.ConcurrentBag_CopyTo_ArgumentNullException);
}
if (index < 0)
{
throw new ArgumentOutOfRangeException(nameof(index), SR.Collection_CopyTo_ArgumentOutOfRangeException);
}
// Short path if the bag is empty
// 当没有元素时直接返回
if (_workStealingQueues == null)
{
return;
}
bool lockTaken = false;
try
{
FreezeBag(ref lockTaken);
// Make sure we won't go out of bounds on the array
// 确保操作不会超出数组的范围
int count = DangerousCount;
if (index > array.Length - count)
{
throw new ArgumentException(SR.Collection_CopyTo_TooManyElems, nameof(index));
}
// Do the copy
// 开始复制
try
{
//
int copied = CopyFromEachQueueToArray(array, index);
// 校验是否复制前后记录的数据数量相等
Debug.Assert(copied == count);
}
catch (ArrayTypeMismatchException e)
{
// Propagate same exception as in desktop
throw new InvalidCastException(e.Message, e);
}
}
finally
{
UnfreezeBag(lockTaken);
}
}
/// <summary>
/// Copies from each queue to the target array, starting at the specified index.
///
/// </summary>
private int CopyFromEachQueueToArray(T[] array, int index)
{
Debug.Assert(Monitor.IsEntered(GlobalQueuesLock));
int i = index;
for (WorkStealingQueue queue = _workStealingQueues; queue != null; queue = queue._nextQueue)
{
i += queue.DangerousCopyTo(array, i);
}
return i - index;
}
/// <summary>
/// Copies the elements of the <see cref="T:System.Collections.ICollection"/> to an <see
/// cref="T:System.Array"/>, starting at a particular
/// <see cref="T:System.Array"/> index.
/// </summary>
/// <param name="array">The one-dimensional <see cref="T:System.Array">Array</see> that is the
/// destination of the elements copied from the
/// <see cref="ConcurrentBag{T}"/>. The <see
/// cref="T:System.Array">Array</see> must have zero-based indexing.</param>
/// <param name="index">The zero-based index in <paramref name="array"/> at which copying
/// begins.</param>
/// <exception cref="ArgumentNullException"><paramref name="array"/> is a null reference (Nothing in
/// Visual Basic).</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is less than
/// zero.</exception>
/// <exception cref="ArgumentException">
/// <paramref name="array"/> is multidimensional. -or-
/// <paramref name="array"/> does not have zero-based indexing. -or-
/// <paramref name="index"/> is equal to or greater than the length of the <paramref name="array"/>
/// -or- The number of elements in the source <see cref="T:System.Collections.ICollection"/> is
/// greater than the available space from <paramref name="index"/> to the end of the destination
/// <paramref name="array"/>. -or- The type of the source <see
/// cref="T:System.Collections.ICollection"/> cannot be cast automatically to the type of the
/// destination <paramref name="array"/>.
/// </exception>
void ICollection.CopyTo(Array array, int index)
{
// If the destination is actually a T[], use the strongly-typed
// overload that doesn't allocate/copy an extra array.
T[] szArray = array as T[];
if (szArray != null)
{
CopyTo(szArray, index);
return;
}
// Otherwise, fall back to first storing the contents to an array,
// and then relying on its CopyTo to copy to the target Array.
if (array == null)
{
throw new ArgumentNullException(nameof(array), SR.ConcurrentBag_CopyTo_ArgumentNullException);
}
ToArray().CopyTo(array, index);
}
/// <summary>
/// Copies the <see cref="ConcurrentBag{T}"/> elements to a new array.
/// </summary>
/// <returns>A new array containing a snapshot of elements copied from the <see
/// cref="ConcurrentBag{T}"/>.</returns>
public T[] ToArray()
{
if (_workStealingQueues != null)
{
bool lockTaken = false;
try
{
FreezeBag(ref lockTaken);
int count = DangerousCount;
if (count > 0)
{
var arr = new T[count];
int copied = CopyFromEachQueueToArray(arr, 0);
Debug.Assert(copied == count);
return arr;
}
}
finally
{
UnfreezeBag(lockTaken);
}
}
// Bag was empty
return Array.Empty<T>();
}
/// <summary>
/// Removes all values from the <see cref="ConcurrentBag{T}"/>.
/// </summary>
public void Clear()
{
// If there are no queues in the bag, there's nothing to clear.
if (_workStealingQueues == null)
{
return;
}
// Clear the local queue.
WorkStealingQueue local = GetCurrentThreadWorkStealingQueue(forceCreate: false);
if (local != null)
{
local.LocalClear();
if (local._nextQueue == null && local == _workStealingQueues)
{
// If it's the only queue, nothing more to do.
return;
}
}
// Clear the other queues by stealing all remaining items. We freeze the bag to
// avoid having to contend with too many new items being added while we're trying
// to drain the bag. But we can't just freeze the bag and attempt to remove all
// items from every other queue, as even with freezing the bag it's dangerous to
// manipulate other queues' tail pointers and add/take counts.
bool lockTaken = false;
try
{
FreezeBag(ref lockTaken);
for (WorkStealingQueue queue = _workStealingQueues; queue != null; queue = queue._nextQueue)
{
T ignored;
while (queue.TrySteal(out ignored, take: true)) ;
}
}
finally
{
UnfreezeBag(lockTaken);
}
}
/// <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() => new Enumerator(ToArray());
/// <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 items enumerated represent a moment-in-time snapshot of the contents
/// of the bag. It does not reflect any update to the collection after
/// <see cref="GetEnumerator"/> was called.
/// </remarks>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Gets the number of elements contained in the <see cref="ConcurrentBag{T}"/>.
/// </summary>
/// <value>The number of elements contained in the <see cref="ConcurrentBag{T}"/>.</value>
/// <remarks>
/// The count returned 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.
/// </remarks>
public int Count
{
get
{
// Short path if the bag is empty
if (_workStealingQueues == null)
{
return 0;
}
bool lockTaken = false;
try
{
FreezeBag(ref lockTaken);
return DangerousCount;
}
finally
{
UnfreezeBag(lockTaken);
}
}
}
/// <summary>Gets the number of items stored in the bag.</summary>
/// <remarks>Only provides a stable result when the bag is frozen.</remarks>
private int DangerousCount
{
get
{
int count = 0;
for (WorkStealingQueue queue = _workStealingQueues; queue != null; queue = queue._nextQueue)
{
checked
{
count += queue.DangerousCount;
}
}
Debug.Assert(count >= 0);
return count;
}
}
/// <summary>
/// Gets a value that indicates whether the <see cref="ConcurrentBag{T}"/> is empty.
/// </summary>
/// <value>true if the <see cref="ConcurrentBag{T}"/> is empty; otherwise, false.</value>
public bool IsEmpty
{
get
{
// Fast-path based on the current thread's local queue.
WorkStealingQueue local = GetCurrentThreadWorkStealingQueue(forceCreate: false);
if (local != null)
{
// We don't need the lock to check the local queue, as no other thread
// could be adding to it, and a concurrent steal that transitions from
// non-empty to empty doesn't matter because if we see this as non-empty,
// then that's a valid moment-in-time answer, and if we see this as empty,
// we check other things.
if (!local.IsEmpty)
{
return false;
}
// We know the local queue is empty (no one besides this thread could have
// added to it since we checked). If the local queue is the only one
// in the bag, then the bag is empty, too.
if (local._nextQueue == null && local == _workStealingQueues)
{
return true;
}
}
// Couldn't take a fast path. Freeze the bag, and enumerate the queues to see if
// any is non-empty.
bool lockTaken = false;
try
{
FreezeBag(ref lockTaken);
for (WorkStealingQueue queue = _workStealingQueues; queue != null; queue = queue._nextQueue)
{
if (!queue.IsEmpty)
{
return false;
}
}
}
finally
{
UnfreezeBag(lockTaken);
}
// All queues were empty, so the bag was empty.
return true;
}
}
/// <summary>
/// Gets a value indicating whether access to the <see cref="T:System.Collections.ICollection"/> is
/// synchronized with the SyncRoot.
/// </summary>
/// <value>true if access to the <see cref="T:System.Collections.ICollection"/> is synchronized
/// with the SyncRoot; otherwise, false. For <see cref="ConcurrentBag{T}"/>, this property always
/// returns false.</value>
bool ICollection.IsSynchronized => false;
/// <summary>
/// Gets an object that can be used to synchronize access to the <see
/// cref="T:System.Collections.ICollection"/>. This property is not supported.
/// </summary>
/// <exception cref="T:System.NotSupportedException">The SyncRoot property is not supported.</exception>
object ICollection.SyncRoot
{
get { throw new NotSupportedException(SR.ConcurrentCollection_SyncRoot_NotSupported); }
}
/// <summary>Global lock used to synchronize the queues pointer and all bag-wide operations (e.g. ToArray, Count, etc.).</summary>
private object GlobalQueuesLock
{
get
{
Debug.Assert(_locals != null);
return _locals;
}
}
/// <summary>"Freezes" the bag, such that no concurrent operations will be mutating the bag when it returns.</summary>
/// <param name="lockTaken">true if the global lock was taken; otherwise, false.</param>
private void FreezeBag(ref bool lockTaken)
{
// Take the global lock to start freezing the bag. This helps, for example,
// to prevent other threads from joining the bag (adding their local queues)
// while a global operation is in progress.
Debug.Assert(!Monitor.IsEntered(GlobalQueuesLock));
Monitor.Enter(GlobalQueuesLock, ref lockTaken);
WorkStealingQueue
head = _workStealingQueues; // stable at least until GlobalQueuesLock is released in UnfreezeBag
// Then acquire all local queue locks, noting on each that it's been taken.
for (WorkStealingQueue queue = head; queue != null; queue = queue._nextQueue)
{
Monitor.Enter(queue, ref queue._frozen);
}
Interlocked.MemoryBarrier(); // prevent reads of _currentOp from moving before writes to _frozen
// Finally, wait for all unsynchronized operations on each queue to be done.
for (WorkStealingQueue queue = head; queue != null; queue = queue._nextQueue)
{
if (queue._currentOp != (int)Operation.None)
{
var spinner = new SpinWait();
do
{
spinner.SpinOnce();
} while (queue._currentOp != (int)Operation.None);
}
}
}
/// <summary>"Unfreezes" a bag frozen with <see cref="FreezeBag(ref bool)"/>.</summary>
/// <param name="lockTaken">The result of the <see cref="FreezeBag(ref bool)"/> method.</param>
private void UnfreezeBag(bool lockTaken)
{
Debug.Assert(Monitor.IsEntered(GlobalQueuesLock) == lockTaken);
if (lockTaken)
{
// Release all of the individual queue locks.
for (WorkStealingQueue queue = _workStealingQueues; queue != null; queue = queue._nextQueue)
{
if (queue._frozen)
{
queue._frozen = false;
Monitor.Exit(queue);
}
}
// Then release the global lock.
Monitor.Exit(GlobalQueuesLock);
}
}
/// <summary>Provides a work-stealing queue data structure stored per thread.</summary>
private sealed class WorkStealingQueue
{
/// <summary>
/// Initial size of the queue's array.
/// 队列内部数组的初始化长度
/// </summary>
private const int InitialSize = 32;
/// <summary>
/// Starting index for the head and tail indices.
/// 头和尾节点的开始下标
/// </summary>
private const int StartIndex =
#if DEBUG
int.MaxValue; // in debug builds, start at the end so we exercise the index reset logic
#else
0;
#endif
/// <summary>
/// Head index from which to steal. This and'd with the <see cref="_mask"/> is the index into <see cref="_array"/>.
/// 用于计算当前需要被窃取的队头元素索引, _headIndex 与 上 _mask 就是 队头元素在队列中的下标
/// </summary>
private volatile int _headIndex = StartIndex;
/// <summary>
/// Tail index at which local pushes/pops happen. This and'd with the <see cref="_mask"/> is the index into <see cref="_array"/>.
/// 用于计算当前需要被窃取的队尾元素索引, _headIndex 与 上 _mask 就是 队尾元素在队列中的下标
/// </summary>
private volatile int _tailIndex = StartIndex;
/// <summary>
/// The array storing the queue's data.
/// 队列中的内部数组
/// </summary>
private volatile T[] _array = new T[InitialSize];
/// <summary>
/// Mask and'd(位操作中的"与"操作) with <see cref="_headIndex"/> and <see cref="_tailIndex"/> to get an index into <see cref="_array"/>.
/// 用来获取元素在数组中对应下标,具体计算方法为:index = _headIndex & _mask or index = _tailIndex & _mask
/// </summary>
private volatile int _mask = InitialSize - 1;
/// <summary>
/// Numbers of elements in the queue from the local perspective; needs to be combined with <see cref="_stealCount"/> to get an actual Count.
/// 从局部中来看的元素数量,需要结合下面的_stealCount来获取实际的数量
/// </summary>
private int _addTakeCount;
/// <summary>
/// Number of steals; needs to be combined with <see cref="_addTakeCount"/> to get an actual Count.
/// 窃取的数量
/// </summary>
private int _stealCount;
/// <summary>
/// The current queue operation. Used to quiesce before performing operations from one thread onto another.
/// 当前队列的操作做类型,在从一个线程向另一个线程执行操作之前,用于静态化。
/// </summary>
internal volatile int _currentOp;
/// <summary>
/// true if this queue's lock is held as part of a global freeze.
/// 值为true表示:这个队列的锁作为全局冻结的一部分被持有
/// </summary>
internal bool _frozen;
/// <summary>
/// Next queue in the <see cref="ConcurrentBag{T}"/>'s set of thread-local queues.
/// 表示当前队列在ThreadLocal中的下一个队列
/// </summary>
internal readonly WorkStealingQueue _nextQueue;
/// <summary>
/// Thread ID that owns this queue.
/// 拥有这个队列的线程ID
/// </summary>
internal readonly int _ownerThreadId;
/// <summary>
/// Initialize the WorkStealingQueue.
/// 初始化
/// </summary>
/// <param name="nextQueue">
/// The next queue in the linked list of work-stealing queues.
/// 当前队列在链表中的下一个队列
/// </param>
internal WorkStealingQueue(WorkStealingQueue nextQueue)
{
// 当前线程ID
_ownerThreadId = Environment.CurrentManagedThreadId;
// 链表中的下一个队列
_nextQueue = nextQueue;
}
/// <summary>
/// Gets whether the queue is empty.
/// 队列是否为空
/// </summary>
internal bool IsEmpty
{
get
{
/// _tailIndex can be decremented even while the bag is frozen, as the decrement in TryLocalPop happens prior
/// to the check for _frozen. But that's ok, as if _tailIndex is being decremented such that _headIndex becomes
/// >= _tailIndex, then the queue is about to be empty. This does mean, though, that while holding the lock,
/// it is possible to observe Count == 1 but IsEmpty == true. As such, we simply need to avoid doing any operation
/// while the bag is frozen that requires those values to be consistent.
/// _tailIndex可以被减少即使处于冻结状态,虽然减少操作TryLoclPop()的优先级大于检查_frozen的优先级。
/// 那也没事,当_tailIndex 被减1时,如果满足_headIndex >= _tainIndex, 表明队列已经为空。
/// 那也就意味着,即使持有锁,并且Count == 1,但是 IsEmpty == true也可能出现。
/// 即使可以支持这样的操作,当被冻结时,我们也要尽量避免出现类似的操作,因为冻结意味着那些数据不会被外部改变。
// 当头索引大于等于尾索引时表示队列为空
return _headIndex >= _tailIndex;
}
}
/// <summary>
/// Add new item to the tail of the queue.
/// 添加元素到栈顶
/// </summary>
/// <param name="item">
/// The item to add.
/// 被添加的元素
/// </param>
internal void LocalPush(T item, ref long emptyToNonEmptyListTransitionCount)
{
// Log
Debug.Assert(Environment.CurrentManagedThreadId == _ownerThreadId);
// 是否拿到锁, 默认为false表示未得到锁
bool lockTaken = false;
try
{
// Full fence to ensure subsequent reads don't get reordered before this
// 设置当前操作为添加操作,这个可以保证原子性
Interlocked.Exchange(ref _currentOp, (int)Operation.Add);
// 队尾索引
int tail = _tailIndex;
// Rare corner case (at most once every 2 billion pushes on this thread):
// 很少的几率会出现
// We're going to increment the tail; if we'll overflow, then we need to reset our counts
// 我们准备对队尾进行加操作;如果发生溢出,我们将会重置计数变量
// 这里将会对tail执行增加操作,但是当数组
if (tail == int.MaxValue)
{
// set back to None temporarily to avoid a deadlock
// 临时回退到None操作,这样可以避免死锁
_currentOp = (int)Operation.None;
lock (this)
{
// 检查是否有Add操作发生
Debug.Assert(_tailIndex == tail, "No other thread should be changing _tailIndex");
// TODO
// Rather than resetting to zero, we'll just mask off the bits we don't care about.
// This way we don't need to rearrange the items already in the queue; they'll be found
// correctly exactly where they are. One subtlety here is that we need to make sure that
// if head is currently < tail, it remains that way. This happens to just fall out from
// the bit-masking, because we only do this if tail == int.MaxValue, meaning that all
// bits are set, so all of the bits we're keeping will also be set. Thus it's impossible
// for the head to end up > than the tail, since you can't set any more bits than all of them.
// 计算元素的头尾索引
_headIndex = _headIndex & _mask;
_tailIndex = tail = tail & _mask;
// 检查是否为空或有数据
Debug.Assert(_headIndex <= _tailIndex);
// ensure subsequent reads aren't reordered before this
// 确保在这个期间的读操作不会被记录
Interlocked.Exchange(ref _currentOp,
(int)Operation.Add);
}
}
// TODO
// We'd like to take the fast path that doesn't require locking, if possible. It's not possible if:
// - another thread is currently requesting that the whole bag synchronize, e.g. a ToArray operation
// - if there are fewer than two spaces available. One space is necessary for obvious reasons:
// to store the element we're trying to push. The other is necessary due to synchronization with steals.
// A stealing thread first increments _headIndex to reserve the slot at its old value, and then tries to
// read from that slot. We could potentially have a race condition whereby _headIndex is incremented just
// before this check, in which case we could overwrite the element being stolen as that slot would appear
// to be empty. Thus, we only allow the fast path if there are two empty slots.
// - if there <= 1 elements in the list. We need to be able to successfully track transitions from
// empty to non-empty in a way that other threads can check, and we can only do that tracking
// correctly if we synchronize with steals when it's possible a steal could take the last item
// in the list just as we're adding. It's possible at this point that there's currently an active steal
// operation happening but that it hasn't yet incremented the head index, such that we could read a smaller
// than accurate by 1 value for the head. However, only one steal could possibly be doing so, as steals
// take the lock, and another steal couldn't then increment the header further because it'll see that
// there's currently an add operation in progress and wait until the add completes.
// read after _currentOp set to Add
// 在_currentOp被设置为Add后再读
int head = _headIndex;
// !_frozen: 没有被局部冻结
// head < tail - 1 :
// tail < (head + _mask) :
// 关于 && 与 & 的区别 请自行学习
if (!_frozen && head < tail - 1 & tail < (head + _mask))
{
// 将元素放入对应的位置
_array[tail & _mask] = item;
// 栈顶索引指向栈顶元素的下一个位置
_tailIndex = tail + 1;
}
else
{
// We need to contend with foreign operations (e.g. steals, enumeration, etc.), so we lock.
_currentOp = (int)Operation.None; // set back to None to avoid a deadlock
Monitor.Enter(this, ref lockTaken);
// this count is stable, as we're holding the lock
// 这些数都是有原子性保证的
head = _headIndex;
int count = tail - head;
// If we're full, expand the array.
// 如果栈已经满了,就需要扩张
if (count >= _mask)
{
// Expand the queue by doubling its size.
// 扩张的长度尾原来的2倍
var newArray = new T[_array.Length << 1];
int headIdx = head & _mask;
if (headIdx == 0)
{
// 直接从0开始复制, 长度为_array的数组长度
Array.Copy(_array, 0, newArray, 0, _array.Length);
}
else
{
// 分段复制
// 先将_array下标从headIdx 到 _array末尾的元素复制到newArray中,在newArray中的下标为从0到_array.Length - 1
Array.Copy(_array, headIdx, newArray, 0, _array.Length - headIdx);
// 再将_array下标从0到 headIdx - 1的元素复制到newArray中,在newArray中的下标为从_array.Length - headIdx 到 _array.Length
Array.Copy(_array, 0, newArray, _array.Length - headIdx, headIdx);
}
// Reset the field values
// 重置下面的变量
_array = newArray;
// 队头指向0
_headIndex = 0;
// 队尾指向队尾元素的下一个位置
_tailIndex = tail = count;
// 将右边第一个位置为1
/// 假设 _mask之前为 3:
/// 3 : 0011
/// (_mark << 1) : 0110
/// (_mark << 1) | 1 : 0111
/// 至于为什么怎么做那?试想一下如果添加元素时tail为1那么,在进行 (tail & _mask)计算是得到的结果将为0,而不是真正的下标1
_mask = (_mask << 1) | 1;
}
// Add the element
// 添加一个元素
_array[tail & _mask] = item;
// 队尾向后移动
_tailIndex = tail + 1;
// Now that the item has been added,
// 走到这里说明已经至少添加了一个元素了,
// if we were at 0 (now at 1) item, increase the empty to non-empty transition count.
// 如果之前没有任何元素(相对现在来说加了一个元素),那么这种情况属于队列从空队列到非空队列的情况, 当发生这种转换时需要累加一下传进来的ref 类型参数emptyToNonEmptyListTransitionCount,
// 那为什么怎么做?可以查看TrySteal()中的调用,用于循环查找
if (count == 0)
{
// We just transitioned from empty to non-empty, so increment the transition count.
//这里只处理从空队列到非空队列的情况,所以对其进行原子累加操作
// 关于 ref的解释大家可以自行学习,不了解的可以把它当成一个指针,或则是一个可以改变自身值变量,即使它是一个基本类型也不例外
Interlocked.Increment(ref emptyToNonEmptyListTransitionCount);
}
// Update the count to avoid overflow.
// 更新添加任务的数量,防止类型溢出,
// We can trust _stealCount here, as we're inside the lock and it's only manipulated there.
// 由于这里上了锁,所以这个处理过程是安全的
// 另外关于——stealCount的值变化,可以查看TrySteal()中代码
_addTakeCount -= _stealCount;
// 清空窃取值
_stealCount = 0;
}
// Increment the count from the add/take perspective
// 增加元素数量,同样在TryLocalPop()也会减少该值
// 使用checked可以保证当_addTakeCount超过int最大值时,会被置为0,而不是溢出后的值
checked
{
_addTakeCount++;
}
}
finally
{
// 将当前操作置为无操作状态
_currentOp = (int)Operation.None;
// 如果之前对Monitor上锁成功,在这里对它进行解锁
if (lockTaken)
{
Monitor.Exit(this);
}
}
}
/// <summary>
/// Clears the contents of the local queue.
/// 清空当前队列
/// </summary>
internal void LocalClear()
{
Debug.Assert(Environment.CurrentManagedThreadId == _ownerThreadId);
// synchronize with steals
// 锁住当前 WorkStealingQueue
lock (this)
{
// If the queue isn't empty, reset the state to clear out all items.
// 如果当前队列不为空,重置当前状态并且清空所有元素
if (_headIndex < _tailIndex)
{
// 都指向起始默认值
_headIndex = _tailIndex = StartIndex;
// 任务数赋值为0
_addTakeCount = _stealCount = 0;
// 清空内部数组中的元素
Array.Clear(_array, 0, _array.Length);
}
}
}
/// <summary>
/// Remove an item from the tail of the queue.
/// 移除栈顶元素
/// </summary>
/// <param name="result">
/// The removed item
/// 返回被移除的元素
/// </param>
internal bool TryLocalPop(out T result)
{
// Log
Debug.Assert(Environment.CurrentManagedThreadId == _ownerThreadId);
int tail = _tailIndex;
// 如果队列中没有元素,直接返回T的默认值,和false
if (_headIndex >= tail)
{
result = default(T);
return false;
}
// 锁标记
bool lockTaken = false;
try
{
// Decrement the tail using a full fence to ensure subsequent reads don't reorder before this.
// If the read of _headIndex moved before this write to _tailIndex, we could erroneously end up
// popping an element that's concurrently being stolen, leading to the same element being
// dequeued from the bag twice.
// 设置当前为为取操作
_currentOp = (int)Operation.Take;
// 队尾索引减一,指向队尾元素
Interlocked.Exchange(ref _tailIndex, --tail);
// If there is no interaction with a steal, we can head down the fast path.
// Note that we use _headIndex < tail rather than _headIndex <= tail to account
// for stealing peeks, which don't increment _headIndex, and which could observe
// the written default(T) in a race condition to peek at the element.
// 没有被冻结,并且当前队列有元素
if (!_frozen && _headIndex < tail)
{
// 获取栈顶索引
int idx = tail & _mask;
// 获取栈顶元素
result = _array[idx];
// 覆盖取出位置
_array[idx] = default(T);
// 减少栈中元素数
_addTakeCount--;
return true;
}
else
{
// Interaction with steals: 0 or 1 elements left.
// 处理其它线程对窃取当前线程元素的情况, 一般情况进到这里只有至多一个元素
// set back to None to avoid a deadlock
// 为了避免死锁,将当前操作设为None
_currentOp = (int)Operation.None;
// 上锁
Monitor.Enter(this, ref lockTaken);
if (_headIndex <= tail)
{
// Element still available. Take it.
// 在!_frozen的情况下,但是仍然有元素可以获取
// 元素索引
int idx = tail & _mask;
// 元素值
result = _array[idx];
// 覆盖原来的位置
_array[idx] = default(T);
// 减少栈中元素数
_addTakeCount--;
return true;
}
else
{
// We encountered a race condition and the element was stolen, restore the tail.
// 这里由于元素已经被窃取,而当前线程没有获取到,
_tailIndex = tail + 1;
result = default(T);
return false;
}
}
}
finally
{
// 保证最后操作被置为None
_currentOp = (int)Operation.None;
if (lockTaken)
{
Monitor.Exit(this);
}
}
}
/// <summary>
/// Peek an item from the tail of the queue.
/// 查看队列尾部的元素
/// </summary>
/// <param name="result">
/// the peeked item
/// 指向被查看的元素
/// </param>
/// <returns>
/// True if succeeded, false otherwise
/// 如果result设置成功,则返回true,否则返回false
/// </returns>
internal bool TryLocalPeek(out T result)
{
Debug.Assert(Environment.CurrentManagedThreadId == _ownerThreadId);
int tail = _tailIndex;
// 如果队列中有元素
if (_headIndex < tail)
{
// It is possible to enable lock-free peeks, following the same general approach
// that's used in TryLocalPop. However, peeks are more complicated as we can't
// do the same kind of index reservation that's done in TryLocalPop; doing so could
// end up making a steal think that no item is available, even when one is. To do
// it correctly, then, we'd need to add spinning to TrySteal in case of a concurrent
// peek happening. With a lock, the common case (no contention with steals) will
// effectively only incur two interlocked operations (entering/exiting the lock) instead
// of one (setting Peek as the _currentOp). Combined with Peeks on a bag being rare,
// for now we'll use the simpler/safer code.
// 双重检查 Double Lock Check
lock (this)
{
if (_headIndex < tail)
{
// 如果获得lock后,队列中依然存在元素
result = _array[(tail - 1) & _mask];
return true;
}
}
}
// 否则返回T的默认值
result = default(T);
// 返回false
return false;
}
/// <summary>
/// Steal an item from the head of the queue.
/// 从队头窃取一个元素
/// </summary>
/// <param name="result">
/// the removed item
/// 被窃取的元素
/// </param>
/// <param name="take">
/// true to take the item; false to simply peek at it
/// true: 表示获取头元素成功,并且在队列中删除该元素
/// false: 表示查看头元素,不会再队列中删除该元素
/// </param>
internal bool TrySteal(out T result, bool take)
{
lock (this)
{
// _headIndex is only manipulated under the lock
int head = _headIndex;
if (take)
{
// If there are <= 2 items in the list, we need to ensure no add operation
// is in progress, as add operations need to accurately count transitions
// from empty to non-empty, and they can only do that if there are no concurrent
// steal operations happening at the time.
// 获取、窃取元素的情况下
if (head < _tailIndex - 1 && _currentOp != (int)Operation.Add)
{
var spinner = new SpinWait();
do
{
spinner.SpinOnce();
} while (_currentOp == (int)Operation.Add);
}
// Increment head to tentatively take an element: a full fence is used to ensure the read
// of _tailIndex doesn't move earlier, as otherwise we could potentially end up stealing
// the same element that's being popped locally.
Interlocked.Exchange(ref _headIndex, unchecked(head + 1));
// If there's an element to steal, do it.
// 如果存在元素可以窃取,就拿走它
if (head < _tailIndex)
{
int idx = head & _mask;
result = _array[idx];
_array[idx] = default(T);
_stealCount++;
return true;
}
else
{
// We contended with the local thread and lost the race, so restore the head.
// 当遇到竞争时,并且没有竞争到这个元素,直接恢复头节点
_headIndex = head;
}
}
else if (head < _tailIndex)
{
// Peek, if there's an element available
// 如果是查看的情况,只要存在元素返回即可
result = _array[head & _mask];
return true;
}
}
// The queue was empty.
// 队列中没有元素
result = default(T);
return false;
}
/// <summary>
/// Copies the contents of this queue to the target array starting at the specified index.
/// 从一个指定的位置开始复制队列中的元素到 输出数组(arraj'jy) 中
/// </summary>
internal int DangerousCopyTo(T[] array, int arrayIndex)
{
// 断言判断,将不符合的一些写入log或堆栈信息中
Debug.Assert(Monitor.IsEntered(this));
Debug.Assert(_frozen);
Debug.Assert(array != null);
Debug.Assert(arrayIndex >= 0 && arrayIndex <= array.Length);
// 获取当前对头索引
int headIndex = _headIndex;
// 获取当前队列中的元素个数
int count = DangerousCount;
Debug.Assert(
count == (_tailIndex - _headIndex) ||
count == (_tailIndex + 1 - _headIndex),
"Count should be the same as tail - head, but allowing for the possibility that " +
"a peek decremented _tailIndex before seeing that a freeze was happening.");
Debug.Assert(arrayIndex <= array.Length - count);
// Copy from this queue's array to the destination array, but in reverse order to match the ordering of desktop.
// 复制队列中的元素到 输出数组(array),但顺序相反,刚好符合 数组下标大的位置放置最先进入队列的人
for (int i = arrayIndex + count - 1; i >= arrayIndex; i--)
{
array[i] = _array[headIndex++ & _mask];
}
// 返回复制的数量(如果发生多线程操作,数目可能不是真正的队列长度)
return count;
}
/// <summary>
/// Gets the total number of items in the queue.
/// 获取队列中的元素个数
/// </summary>
/// <remarks>
/// This is not thread safe, only providing an accurate result either from the owning
/// thread while its lock is held or from any thread while the bag is frozen.
/// 这个操作不是线程安全的,仅仅提供了一个相对准确的结果,获取结果的时候,当前队列可能处于被当前线程持有(加锁)的状态,或则当前包冻结的状态,此时其它其线程持有该队列(对该队列加锁)
/// </remarks>
internal int DangerousCount
{
get
{
// 输出当前线程是否持有这个队列
Debug.Assert(Monitor.IsEntered(this));
// 数量数量
int count = _addTakeCount - _stealCount;
Debug.Assert(count >= 0);
return count;
}
}
}
/// <summary>
/// Lock-free operations performed on a queue.
/// 队列状态操作标识符
/// </summary>
internal enum Operation
{
None,
Add,
Take
};
/// <summary>
/// Provides an enumerator for the bag.
/// 提供一个迭代器编列这个队列
/// </summary>
/// <remarks>
/// The original implementation of ConcurrentBag used a <see cref="List{T}"/> as part of
/// the GetEnumerator implementation.
/// ConcurrentBag使用一个List容器作为迭代器实现的一部分
///
/// That list was then changed to be an array,
/// but array's GetEnumerator has different behavior than does list's,
/// in particular for the case where Current is used after MoveNext returns false.
/// list曾经被改成数组,但是数组返回的迭代器 相比 list返回的 有不同的行为
/// 特别是在以下情况下情况 在MoveNext()返回false的时候使用Current
///
/// To avoid any concerns around compatibility,
/// we use a custom enumerator rather than just returning array's.
/// 为了与其它相关内容保持兼容,我们在这里使用传统形式的迭代器来代替返回一个数组形式的迭代器
///
/// This enumerator provides the essential elements of both list's and array's enumerators.
/// 这里的迭代器可被用于list或则数组的迭代器
/// </remarks>
private sealed class Enumerator : IEnumerator<T>
{
// 只读数组
private readonly T[] _array;
// 存储当前元素
private T _current;
// 存储当前下标
private int _index;
/// <summary>
/// 迭代器钩构造
/// </summary>
/// <param name="array">需要传入一个数组</param>
public Enumerator(T[] array)
{
Debug.Assert(array != null);
_array = array;
}
/// <summary>
/// 移动到下一个元素位置,并且设置<see cref="_current"/> 和 <see cref="_index"/>
/// </summary>
/// <returns>
/// false: 无元素可以读取
/// true: 可与读取元素
/// </returns>
public bool MoveNext()
{
// 当前读取在规定范围
if (_index < _array.Length)
{
// 将当前数组元素赋给 _current
_current = _array[_index++];
// 设置移动状态
return true;
}
// 读取超出返回
_index = _array.Length + 1;
// 设置移动状态,不可获取
return false;
}
/// <summary>
/// 返回当前元素
/// </summary>
public T Current => _current;
/// <summary>
/// 返回当前元素
/// </summary>
/// <exception cref="InvalidOperationException"></exception>
object IEnumerator.Current
{
get
{
if (_index == 0 || _index == _array.Length + 1)
{
throw new InvalidOperationException(SR
.ConcurrentBag_Enumerator_EnumerationNotStartedOrAlreadyFinished);
}
return Current;
}
}
/// <summary>
/// 从头开始遍历
/// </summary>
public void Reset()
{
// 下标指向 0
_index = 0;
// 当前元素设置为T的默认值
_current = default(T);
}
/// <summary>
/// 可以做一些销毁操作
/// </summary>
public void Dispose()
{
}
}
}
}