C# 多線程之List的線程安全問題


網上關於List的線程安全問題將的很少,所以自己實驗了一把,發現確實是線程不安全的.所以當你在進行多線程編程中使用了共享的List集合,必須對其進行線程安全處理.

List的Add方法是線程不安全的,List的源碼中的Add方法,使用了每次當當前的元素達到上限,通過創建一個新的數組實例,並給長度翻倍的操作.如果單線程操作不會有問題,直接擴容,然后繼續往里面加值。下面是List的Add方法和核心邏輯.

也就是說,當多個線程同時添加元素,且剛好它們都執行到了擴容這個階段,當一個線程擴大了這個數組的長度,且進行了+1操作后,另外一個線程剛好也在執行擴容的操作,這個時候它給Capacity的值設為2048,但是另外一個線程已經將this._size設為2049了,所以這個時候就報異常了.當然不止這一個問題,還有Copy的時候也會出問題,如果里面的元素過多,另外一個線程拿到空值的幾率很大.

代碼重現:

    class Program
    {
        static List<long> list = new List<long>();
        static void Main(string[] args)
        {
            var t = Task.Run(() =>
            {
                var tf = new TaskFactory(TaskCreationOptions.AttachedToParent, TaskContinuationOptions.AttachedToParent);
                var childTasks = new Task[]
                {
                    tf.StartNew(()=>Task_0()),
                    tf.StartNew(()=>Task_1()),
                    tf.StartNew(()=>Task_2())
                };
                var tfTask=tf.ContinueWhenAll(childTasks, completedTasks => completedTasks.Where(w => !w.IsFaulted && !w.IsCanceled), TaskContinuationOptions.None);
                tfTask.ContinueWith(task=>
                {
                    var a = list;
                });
            });
            Console.ReadKey();
        }

        static void Task_0()
        {
            for (var i = 0; i < 1000000; i++)
            {
                list.Add(i);
            }
        }

        static void Task_1()
        {
            for (var i = 0; i < 1000000; i++)
            {
                list.Add(i);
            }
        }

        static void Task_2()
        {
            for (var i = 0; i < 1000000; i++)
            {
                list.Add(i);
            }
        }
    }

多跑幾次這段代碼,你幾乎可以重現所有可能出現的多線程資源爭用異常.

解決方案:給Add方法加鎖,代碼如下:

 

    class Program
    {

        static object lockObj = new object();
        static List<long> list = new List<long>();
        static void Main(string[] args)
        {
            var t = Task.Run(() =>
            {
                var tf = new TaskFactory(TaskCreationOptions.AttachedToParent, TaskContinuationOptions.AttachedToParent);
                var childTasks = new Task[]
                {
                    tf.StartNew(()=>Task_0()),
                    tf.StartNew(()=>Task_1()),
                    tf.StartNew(()=>Task_2())
                };
                var tfTask=tf.ContinueWhenAll(childTasks, completedTasks => completedTasks.Where(w => !w.IsFaulted && !w.IsCanceled), TaskContinuationOptions.None);
                tfTask.ContinueWith(task=>
                {
                    var a = list;
                });
            });
            Console.ReadKey();
        }

        static void Task_0()
        {
            for (var i = 0; i < 1000000; i++)
            {
                lock (lockObj)
                {
                    list.Add(i);
                }
            }
        }

        static void Task_1()
        {
            for (var i = 0; i < 1000000; i++)
            {
                lock (lockObj)
                {
                    list.Add(i);
                }
            }
        }
        static void Task_2()
        {
            for (var i = 0; i < 1000000; i++)
            {
                lock (lockObj)
                {
                    list.Add(i);
                }
            }
        }
    }

ok,解決了問題,當然這不是最好的解決方案,你完全可以通過適配器模式,去擴展一個線程安全的List類型,這里我就不寫了.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM