C# 多線程編程第二步——線程同步與線程安全


上一篇博客學習了如何簡單的使用多線程。其實普通的多線程確實很簡單,但是一個安全的高效的多線程卻不那么簡單。所以很多時候不正確的使用多線程反倒會影響程序的性能。

下面先看一個例子 :

   class Program
    {
        static int num = 1;

        static void Main(string[] args)
        {
            Stopwatch stopWatch = new Stopwatch();

            //開始計時
            stopWatch.Start();

            ThreadStart threadStart = new ThreadStart(Run);

            for (int i = 0; i < 5; i++)
            {
                Thread thread = new Thread(threadStart);
                thread.Start();
            }

            num++;
            Console.WriteLine("num is:" + num);
            Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());

            //停止計時
            stopWatch.Stop();

            //輸出執行的時間,毫秒數
            Console.WriteLine("The execution time is " + stopWatch.ElapsedMilliseconds + " milliseconds.");
            Console.ReadKey();
        }

        public static void Run()
        {
            num++;
            Console.WriteLine("num is:" + num);
            Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
        }
    }

執行結果:

從上面可以看出變量 num 的值不是連續遞增的,輸出也是沒有順序的,而且每次輸出的值都是不一樣的,這是因為異步線程同時訪問一個成員時造成的,所以這樣的多線程對於我們來說是不可控的。以上這個例子就是非線程安全的,那么要做到線程安全就需要用到線程同步。線程同步有很多種方法,比如之前用到過的 Join() 方法,它也可以實現線程的同步。下面我們來試試:

   class Program
    {
        static int num = 1;

        static void Main(string[] args)
        {
            Stopwatch stopWatch = new Stopwatch();

            //開始計時
            stopWatch.Start();

            ThreadStart threadStart = new ThreadStart(Run);

            for (int i = 0; i < 5; i++)
            {
                Thread thread = new Thread(threadStart);
                thread.Start();
                thread.Join();
            }

            num++;
            Console.WriteLine("num is:" + num);
            Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());

            //停止計時
            stopWatch.Stop();

            //輸出執行的時間,毫秒數
            Console.WriteLine("The execution time is " + stopWatch.ElapsedMilliseconds + " milliseconds.");
            Console.ReadKey();
        }

        public static void Run()
        {
            num++;
            Console.WriteLine("num is:" + num);
            Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
        }
    }

執行結果:

這樣就實現了簡單的同步,相比起上面的代碼也就只是添加了一行代碼(thread.Join();),之前也提到了 Join() 這個方法用於阻止當前線程,直到前面的線程執行完成。可是這樣雖然是實現了同步,但是卻也阻塞了主線程的繼續執行,這樣和單線程貌似沒什么區別了。既然這樣我們再去學習一下其他的方法。

實現線程同步還有一種鎖的機制,下面是一種最簡單的鎖機制,即使用 lock。如下:

   class Program
    {
        private object locker = new object();
        int num = 1;

        static void Main(string[] args)
        {
            Program program = new Program();
            Stopwatch stopWatch = new Stopwatch();

            //開始計時
            stopWatch.Start();

            ThreadStart threadStart = new ThreadStart(program.Run);

            for (int i = 0; i < 5; i++)
            {
                Thread thread = new Thread(threadStart);
                thread.Start();
            }

            program.num++;
            Console.WriteLine("num is:" + program.num);
            Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());

            //停止計時
            stopWatch.Stop();

            //輸出執行的時間,毫秒數
            Console.WriteLine("The execution time is " + stopWatch.ElapsedMilliseconds + " milliseconds.");
            Console.ReadKey();
        }

        public void Run()
        {
            lock (locker)
            {
                num++;
                Console.WriteLine("num is:" + num);
                Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
            }
        }
    }

執行結果:

lock 是一種比較好用的簡單的線程同步方式,它是通過為給定對象獲取互斥鎖來實現同步的。可以看到這種方式的確沒有阻塞主線程,而且成員變量的值也是連續遞增的,說明是線程安全的。lock 鎖機制表示在同一時刻只有一個線程可以鎖定同步對象(在這里是locker),任何競爭的的其它線程都將被阻止,直到這個鎖被釋放。

lock 的參數必須是基於引用類型的對象,不要是基本類型,比如 bool、int,這樣根本不能同步,原因是lock的參數要求是對象,如果傳入 int,勢必要發生裝箱操作,這樣每次lock的都將是一個新的不同的對象。最好避免使用public類型或不受程序控制的對象實例,因為這樣很可能導致死鎖。永遠也不要 lock 一個字符串。

 

暫時先到這里,后面學了其他方法在繼續更新。

 


免責聲明!

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



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