Core源码(十三)Stack和ConcurrentStack
Stack
Stack(栈)表示对象的后进先出 (LIFO) 集合。实现了ICollection接口。这个数据结构是.net集中基础数据集合中实现起来最简单的。Stack内部是是维护的数组来存储实际的存入的数据,然后对外开放一些方法来操作栈。
概念
定义:限定仅在表尾进行插入或删除操作的线性表,表尾对应栈顶,表头对应栈底,不含元素的栈称为空栈。
入栈:往栈顶插入一个元素。
出栈:在栈顶删除一个元素
元素的操作只能在栈顶进行,最后入栈的元素最先出栈,结构图如下:
属性和变量
/// <summary> /// Storage for MyStack elements /// </summary> private Object[] _array; /// <summary> /// Number of items in the MyStack. /// </summary> private int _size; public virtual int Count { get { return _size; } } // Used to keep enumerator in [....] w/ collection. private int _version; [NonSerialized] private Object _syncRoot; /// <summary> /// 默认容量 /// </summary> private const int _defaultCapacity = 10;
构造函数
Stack有三个构造函数,分别为:
(1)全部使用Stack设定的默认值,不推荐使用(原因是动态扩容需要额外的计算与开辟新的内存空间,动态扩容应该发生在超出预期容量值范围的情况下,抑制溢出);
public Stack() { _array = _emptyArray; _size = 0; _version = 0; }
(2)使用一个非负的整数设定Stack的一个初始容量,推荐使用(给定一个预期的容量值,若预期值小了,会自动扩容,也不担心溢出)
public Stack(int capacity) { if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_NeedNonNegNumRequired); _array = new T[capacity]; _size = 0; _version = 0; }
(3)使用一个现有的非空引用集合进行填充初始化Stack,Stack具有与该集合相同的长度,并且按照集合元素的存储顺序(推荐使用实现了Collection泛型接口的集合,可以获得一些性能上的提升)。
public Stack(IEnumerable<T> collection) { if (collection==null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection); ICollection<T> c = collection as ICollection<T>; if( c != null) { //实现了ICollection泛型接口的集合,简单粗暴,分配空间,数组复制 int count = c.Count; _array = new T[count]; c.CopyTo(_array, 0); _size = count; } else { _size = 0; //没有实现ICollection泛型接口的,默认初始容量为4,空间不够还需动态扩容 _array = new T[_defaultCapacity]; //遍历元素 执行入栈操作 using(IEnumerator<T> en = collection.GetEnumerator()) { while(en.MoveNext()) { Push(en.Current); } } } }
入栈出栈
// Pushes an item to the top of the MyStack. public virtual void Push(Object obj) { if (_size == _array.Length) { //以二倍新增 Object[] newArray = new Object[2 * _array.Length]; Array.Copy(_array, 0, newArray, 0, _size); _array = newArray; } _array[_size++] = obj; _version++; } // Pops an item from the top of the MyStack. If the MyStack is empty, Pop // throws an InvalidOperationException. public virtual Object Pop() { if (_size == 0) throw new InvalidOperationException(); _version++; Object obj = _array[--_size]; _array[_size] = null; // Free memory quicker. return obj; }
ConcurrentStack
原子操作
所谓无锁其实就是在普通栈的实现方式上使用了原子操作,原子操作的原理就是CPU在系统总线上设置一个信号,当其他线程对同一块内存进行访问时CPU监测到该信号存在会,然后当前线程会等待信号释放后才能对内存进行访问。原子操作都是由操作系统API实现底层由硬件支持,常用的操作有:原子递增,原子递减,比较交换,ConcurrentStack中的实现就是使用了原子操作中的比较交换操作。
使用原子操作的好处:
第一、由于没有使用锁,可以避免死锁。
第二、原子操作不会阻塞线程,例如执行某个指令时当前线程挂起了(或执行了一次上下文切换),其他线程还能继续操作,如果使用lock锁,当前线程挂起后由于没有释放锁,其他线程进行操作时会被阻塞。
第三、由于原子操作直接由硬件指令的支持,所以原子操作性能比普通锁的高。
使用原子操作的坏处:
第一,使用原子操作一般失败时会使用回退技术对当前操作进行重试,所以容易产生活锁和线程饥饿问题,但可以通过随机退让等技术进行缓解,但不能消除。
第二,程序员开发使用难度较大,测试难度较大。
下面开始进入正题:
由于.net 中的 ConcurrentStack的代码较多所以本文就不贴出所有代码,本人也只分析笔者认为重要的几个部分,全部源码可以再去以下微软官方网址查看
http://referencesource.microsoft.com/#mscorlib/system/Collections/Concurrent/ConcurrentStack.cs
传统的栈结构都一般都使用单链表实现(.net中的Stack使用的是数组), 入栈操作就是把头节点替换为新节点,出栈操作就是把头结点指向下一个节点。所以当大量线程并发访问时线程的竞争条件都在头结点。也就是说如果我们能保证对于头结点操作时是安全的那么整个栈就是安全的。
这里ConcurrentStack使用私有类Node创建了一个链表。
查询
public bool TryPeek(out T result) { Node head = m_head; // If the stack is empty, return false; else return the element and true. if (head == null) { result = default(T); return false; } else { result = head.m_value; return true; } } public int Count { // 复杂度是O(n)。结果可能在返回前被更新 get { int count = 0; for (Node curr = m_head; curr != null; curr = curr.m_next) { //we don't handle overflow, to be consistent with existing generic collection types in CLR count++; } return count; } } public bool IsEmpty { get { return m_head == null; } }
入栈
入栈的逻辑就是把链表的顶部新增一个元素,指向原来的链头,这里难点是保证线程的安全。
public void Push(T item) { Node newNode = new Node(item); newNode.m_next = m_head; //判断是否相同类型和相同的引用位置,如果相同就转换引用位置。 //相当于m_head指向了newNode if (Interlocked.CompareExchange(ref m_head, newNode, newNode.m_next) == newNode.m_next) { return; } // If we failed, go to the slow path and loop around until we succeed. //如果原子级操作失败,就使用循环,不停的尝试 PushCore(newNode, newNode); } private void PushCore(Node head, Node tail) { SpinWait spin = new SpinWait();111111111 // Keep trying to CAS the exising head with the new node until we succeed. do { spin.SpinOnce(); // Reread the head and link our new node. //把下一个节点替换到最新的m_head tail.m_next = m_head; } //如果是批量的,就是判断尾部的下一个节点 指向原来的头节点 while (Interlocked.CompareExchange( ref m_head, head, tail.m_next) != tail.m_next); }
批量入栈
public void PushRange(T[] items) { if (items == null) { throw new ArgumentNullException("items"); } PushRange(items, 0, items.Length); } public void PushRange(T[] items, int startIndex, int count) { ValidatePushPopRangeInput(items, startIndex, count); // No op if the count is zero if (count == 0) return; Node head, tail; head = tail = new Node(items[startIndex]); //循环生成链表 for (int i = startIndex + 1; i < startIndex + count; i++) { Node node = new Node(items[i]); node.m_next = head; head = node; } tail.m_next = m_head; if (Interlocked.CompareExchange(ref m_head, head, tail.m_next) == tail.m_next) { return; } // If we failed, go to the slow path and loop around until we succeed. PushCore(head, tail); }
出栈
public bool TryPop(out T result) { Node head = m_head; //stack is empty if (head == null) { result = default(T); return false; } if (Interlocked.CompareExchange(ref m_head, head.m_next, head) == head) { result = head.m_value; return true; } // Fall through to the slow path. return TryPopCore(out result); } /// <summary> /// Local helper function to Pop an item from the stack, slow path /// </summary> /// <param name="result">The popped item</param> /// <returns>True if succeeded, false otherwise</returns> private bool TryPopCore(out T result) { Node poppedNode; if (TryPopCore(1, out poppedNode) == 1) { result = poppedNode.m_value; return true; } result = default(T); return false; } private int TryPopCore(int count, out Node poppedHead) { SpinWait spin = new SpinWait(); // Try to CAS the head with its current next. We stop when we succeed or // when we notice that the stack is empty, whichever comes first. Node head; Node next; int backoff = 1; // avoid the case where TickCount could return Int32.MinValue //生成一个随机的计数 Random r = new Random(Environment.TickCount & Int32.MaxValue); while (true) { head = m_head; // Is the stack empty? if (head == null) { poppedHead = null; return 0; } next = head; int nodesCount = 1; for (; nodesCount < count && next.m_next != null; nodesCount++) { next = next.m_next; } // Try to swap the new head. If we succeed, break out of the loop. if (Interlocked.CompareExchange(ref m_head, next.m_next, head) == head) { // Return the popped Node. poppedHead = head; return nodesCount; } // We failed to CAS the new head. Spin briefly and retry. for (int i = 0; i < backoff; i++) { spin.SpinOnce(); } backoff = spin.NextSpinWillYield ? r.Next(1, BACKOFF_MAX_YIELDS) : backoff * 2; } }
遍历
public T[] ToArray() { return ToList().ToArray(); } private List<T> ToList() { List<T> list = new List<T>(); Node curr = m_head; while (curr != null) { list.Add(curr.m_value); curr = curr.m_next; } return list; } public IEnumerator<T> GetEnumerator() { return GetEnumerator(m_head); } private IEnumerator<T> GetEnumerator(Node head) { Node current = head; while (current != null) { yield return current.m_value; current = current.m_next; } }