C# 多線程中的常見問題


1. 資源競爭

當多個同時執行的線程需要同時對全局變量進行寫讀操作時,容易出現資源競爭的問題,導致運行結果出現多種情況。以下面的例子進行說明:

 

        private static CancellationTokenSource cs = new CancellationTokenSource();
        private static int num = 5;
        private static object obj = new object();

        static void Main(string[] args)
        {
            Console.WriteLine("Main Start....");
            Task t1 = Task.Factory.StartNew(Test);
            Task t2 = Task.Factory.StartNew(Test);
            Task.WaitAll(new Task[] { t1, t2 });
            cs.Dispose();
            Console.WriteLine("Main end....");
            Console.ReadLine();
        }

        static void Test()
        {
            while (!cs.IsCancellationRequested)    //是否調用Cancel
            {
                Console.WriteLine("TaskId {0} Excute other code....num is {1}",Task.CurrentId,num);
                if(num == 5){
                    Thread.Sleep(50);        //為了方便觀察,添加延時
                    num++;                    
                    Console.WriteLine("TaskId {0} and Num is {1}", Task.CurrentId, num);
                    if (!cs.IsCancellationRequested)
                    {
                        cs.Cancel();       //取消操作
                    }
                }
            }
        }

 大多數情況,運行結果如下:

Main Start....
TaskId 1 Excute other code....num is 5
TaskId 2 Excute other code....num is 5
TaskId 2 and Num is 6
TaskId 1 and Num is 7
Main end....

任務1 首先運行至Thread.Sleep(50),等待50ms,cpu開始調度任務2運行至Thread.Sleep(50)。 接着任務1 運行num++, 並往控制台輸出結果num=6,然后任務2運行num++, 並往控制台輸出num=7。但有時也會出現下面這種結果:

Main Start....
TaskId 1 Excute other code....num is 5
TaskId 2 Excute other code....num is 5
TaskId 2 and Num is 7
TaskId 1 and Num is 7
Main end....

任務1 首先運行至Thread.Sleep(50),等待50ms,cpu開始調度任務2運行至Thread.Sleep(50)。接着任務1 運行num++,cpu馬上開始調度任務2運行num++,並往控制台輸出num=7,最后調度任務1往控制台輸出num=7。

 解決方式:只需加上線程鎖lock, 便只會出現第一種運行結果,如下:

        static void Test()
        {
            while (!cs.IsCancellationRequested)    //是否調用Cancel
            {
                Console.WriteLine("TaskId {0} Excute other code....num is {1}",Task.CurrentId,num);
                if(num == 5){
                    Thread.Sleep(50);        //為了方便觀察,添加延時
                    lock (obj)               //只有一個線程可以操作
                    {
                        num++;                    
                        Console.WriteLine("TaskId {0} and Num is {1}", Task.CurrentId, num);
                    }
                    if (!cs.IsCancellationRequested)
                    {
                        cs.Cancel();       //取消操作
                    }
                }
            }
        }

 2.線程死鎖

至少有2個線程被掛起,等待對方解鎖,線程將無限等待下去。

 

        private static int num = 5;
        private static object obj1 = new object();
        private static object obj2 = new object();

        static void Main(string[] args)
        {
            Console.WriteLine("Main Start....");
            Parallel.Invoke(LockTest1, LockTest2);
            Console.WriteLine("Main end....");
            Console.ReadLine();
        }

        static void LockTest1()
        {
            lock(obj1){
                lock(obj2){
                    Console.WriteLine("LockTest1 is running");
                }
            }
        }

        static void LockTest2()
        {
            lock (obj2)
            {
                lock (obj1)
                {
                    Console.WriteLine("LockTest2 is running");
                }
            }
        }

 

運行結果:

Main Start....
LockTest1 is running
LockTest2 is running
Main end....

看似正常,但這段程序在極少數的情況下,會出現死鎖。例如CPU先運行LockTest1()中lock(obj1), 馬上又運行LockTest2()中lock(obj2),這時LockTest1()會等待obj2 解鎖,而LockTest2()會等待obj1解鎖 ,形成死鎖。

 

解決方式:在設計程序時,考慮好鎖定的順序,或者定義鎖定超時。


免責聲明!

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



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