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

上一篇博客學習瞭如何簡單的使用多線程。其實普通的多線程確實很簡單,可是一個安全的高效的多線程卻不那麼簡單。因此不少時候不正確的使用多線程反倒會影響程序的性能。html

下面先看一個例子 :安全

   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。以下:spa

   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());
            }
        }
    }

執行結果:pwa

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

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

 

暫時先到這裏,後面學了其餘方法在繼續更新。

相關文章
相關標籤/搜索