线程安全知多少

1. 如何定义线程安全

线程安全,拆开来看:html

  • 线程:指多线程的应用场景下。
  • 安全:指数据安全。

多线程就不用过多介绍了,相关类型集中在System.Threading命名空间及其子命名空间下。
数据,这里特指临界资源
安全,简单来讲就是多线程对某一临界资源进行并发操做时,其最终的结果应和单线程操做的结果保持一致。好比Parallel线程安全问题就是说的这个现象。编程

2. 如何判断是否线程安全

在查MSDN是,咱们常常会看到这样一句话:安全

Thread Safety
Public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.多线程

直译过来就是说:该类型的公共静态成员是线程安全的,但其对应的任何实例成员不确保是线程安全的。并发

你可能对这句话仍是丈二和尚摸不着头脑。我也是。那如今咱们来理一下。less

首先,咱们来理清一下类型和类型成员:
类型是指类自己,类型成员是指类所包含的方法、属性、字段、索引、委托、事件等。类型成员又分为静态成员和实例成员。静态成员,顾名思义就是static关键字修饰的成员。实例成员,就是对类型实例化建立的对象实例才能访问到的成员。源码分析

而后,为何它能够确保全部的公共静态成员是线程安全的呢?是由于它必定经过某种机制,去确保了公共静态成员的线程安全。(这必定是微软源码的一个规范)。
那显而易见,对实例成员,可能因为没有了这样的一个限制,才会说,不确保实例成员是线程安全的。ui

以上只是我我的的一种猜想。那显然仅仅是有猜想仍是不够的,咱们要验证它。而最直接有力的方法莫过于查源码了。this

2.1. StopWatch源码分析

咱们看下System.Diagnostics.StopWatch的源码实现。pwa

在这个类中,主要有如下几个公共静态成员:

  1. public static readonly long Frequency;
  2. public static readonly bool IsHighResolution;
  3. public static Stopwatch StartNew() {//.....}
  4. public static long GetTimestamp() { //....}

首先前两个公共静态字段由于被readonly修饰,只读不可写,因此是线程安全的。
后面两个静态方法由于没有涉及到对临界资源的操做,因此也是线程安全的。
那针对这个StopWatch来讲,保证线程安全的机制是:

  1. 使用readonly修饰公共静态字段
  2. 公共静态方法中不涉及对临界资源的操做。

2.2. ArrayList源码分析

咱们再来看下System.Collections.ArrayList的源码实现。

这个类中,公共静态成员主要是几个静态方法,我简单列举一个:

public static IList ReadOnly(IList list) {
            if (list==null)
                throw new ArgumentNullException("list");
            Contract.Ensures(Contract.Result<IList>() != null);
            Contract.EndContractBlock();
            return new ReadOnlyList(list);
        }

这一个静态方法主要用来建立只读列表,由于不涉及到临界资源的操做,因此线程安全,其余几个静态方法相似。

咱们再来看一个公共实例方法:

private Object[] _items;
        private int _size;
        private int _version;
        public virtual int Add(Object value) {
            Contract.Ensures(Contract.Result<int>() >= 0);
            if (_size == _items.Length) EnsureCapacity(_size + 1);
            _items[_size] = value;
            _version++;
            return _size++;
        }

很显然,对集合进行新增处理时,咱们涉及到对临界资源**_items**的操做,可是这里却没有任何线程同步机制去确保线程安全。因此其实例成员不确保是线程安全的。

2.3. ConcurrentBag源码分析

仅有以上两个例子,不足以验证咱们的猜想。接下来咱们来看看线程安全集合System.Collections.Concurrent.ConcurrentBag的源码实现。
首先咱们来看下MSDN中对ConcurrentBag线程安全的描述:

Thread Safety
All public and protected members of ConcurrentBag are thread-safe and may be used concurrently from multiple threads. However, members accessed through one of the interfaces the ConcurrentBag implements, including extension methods, are not guaranteed to be thread safe and may need to be synchronized by the caller.

这里为何能够自信的保证全部public和protected 成员是线程安全的呢?

一样,咱们仍是来看看对集合进行Add的方法实现:

public void Add(T item)
        {
            // Get the local list for that thread, create a new list if this thread doesn't exist 
            //(first time to call add)
            ThreadLocalList list = GetThreadList(true);
            AddInternal(list, item);
        }

        private ThreadLocalList GetThreadList(bool forceCreate)
        {
            ThreadLocalList list = m_locals.Value;
 
            if (list != null)
            {
                return list;
            }
            else if (forceCreate)
            {
                // Acquire the lock to update the m_tailList pointer
                lock (GlobalListsLock)
                {
                    if (m_headList == null)
                    {
                        list = new ThreadLocalList(Thread.CurrentThread);
                        m_headList = list;
                        m_tailList = list;
                    }
                    else
                    {
 
                        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>
        /// </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
                //Synchronization cases:
                // if the list count is less than two to avoid conflict with any stealing thread
                // if m_needSync is set, this means there is a thread that needs to freeze the bag
                if (list.Count < 2 || m_needSync)
                {
                    // reset it back to zero to avoid deadlock with stealing thread
                    list.m_currentOp = (int)ListOperation.None;
                    Monitor.Enter(list, ref lockTaken);
                }
                list.Add(item, lockTaken);
            }
            finally
            {
                list.m_currentOp = (int)ListOperation.None;
                if (lockTaken)
                {
                    Monitor.Exit(list);
                }
            }
        }

看了源码,就一目了然了。首先使用lock锁获取临界资源list,再使用Moniter锁来进行add操做,保证了线程安全。

至此,咱们对MSDN上常常出现的对Thread Safety的解释,就再也不迷糊了。

若是你仔细看了ConcurrentBag关于Thread Safety的描述的话,后面还有一句:
However, members accessed through one of the interfaces the ConcurrentBag implements, including extension methods, are not guaranteed to be thread safe and may need to be synchronized by the caller.
这又是为何呢,问题就留给你啦。

3. 如何保证线程安全

经过上面分析的几段源码,想必咱们内心也有谱了。
要解决线程安全问题,首先,最重要的是看是否存在临界资源,若是没有,那么就不涉及到线程安全的问题。

若是有临界资源,就须要对临界资源进行线程同步处理了。而关于线程同步的方式,可参考C#编程总结(三)线程同步

另外在书写代码时,为了不潜在的线程安全问题,对于不须要改动的公共静态变量,使用readonly修饰不失为一个很好的方法。

4. 总结

经过以上分析,咱们知道,在多线程的场景下,对于静态成员和实例成员没有绝对的线程安全,其关键在于是否有临界资源

相关文章
相关标签/搜索