細說C#多線程那些事 - 線程同步和多線程優先級


上個文章分享了一些多線程的一些基礎的知識,今天我們繼續學習。

一、Task類

上次我們說了線程池,線程池的QueueUserWorkItem()方法發起一次異步的線程執行很簡單

但是該方法最大的問題是沒有一個內建的機制讓你知道操作什么時候完成,有沒有一個內建的機制在操作完成后獲得一個返回值。為此,可以使用System.Threading.Tasks中的Task類。

Task類在命名空間System.Threading.Tasks下,通過Task的Factory返回TaskFactory類,以TaskFactory.StartNew(Action)方法可以創建一個新的異步線程,所創建的線程默認為后台線程,不會影響前台UI窗口的運行。

如果要取消線程,可以利用CancellationTakenSource對象。如果要在取消任務后執行一個回調方法,則可以使用Task的()方法。

簡單代碼實現:

using System;
using System.Threading.Tasks;

namespace Threading
{
    class Program
    {
        public static Int32 ThreadSum(Int32 n)
        {
            Int32 sum = 0;
            for (; n > 0; --n)
                 sum += n;  
            return sum;
        }
        static void Main(string[] args)
        {
            var t = new Task<Int32>(n => ThreadSum((Int32)n), 100);
            t.Start();
            var cwt = t.ContinueWith(task => Console.WriteLine("The result is {0}", t.Result));
            Console.ReadKey();
        }
    }
}
Task類示例代碼
using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      Task t = Task.Factory.StartNew( () => {
                                  int ctr = 0;
                                  for (ctr = 0; ctr <= 1000000; ctr++)
                                  {}
                                  Console.WriteLine("Finished {0} loop iterations",
                                                    ctr);
                               } );
      t.Wait();
   }
}

更多內容參考: https://msdn.microsoft.com/zh-cn/library/system.threading.tasks.task.aspx

 

二、異步執行

委托的異步執行代碼:BeginInvoke() 和 EndInvoke()

using System;

namespace Threading
{
    public delegate string MyDelegate(object data);
    class Program
    {
        public static string Thread1(object data)
        {
            return data.ToString();
        }
        public static void ThreadCallback(IAsyncResult data)
        {
            Console.WriteLine("ThreadCallback = > " + data.AsyncState);
        }
        static void Main(string[] args)
        {
            var mydelegate = new MyDelegate(Thread1);
            IAsyncResult result = mydelegate.BeginInvoke("Thread1 Para", ThreadCallback, "Callback Para");

            //異步執行完成
            var resultstr = mydelegate.EndInvoke(result);
            Console.WriteLine(resultstr);
            Console.ReadKey();
        }
    }
}
委托異步執行示例代碼

三、線程同步

線程同步:指多個線程協同、協助、互相配合。一些敏感數據不允許被多個線程同時訪問,此時就使用同步訪問技術,保證數據在任何時刻,最多有一個線程訪問,以保證數據的完整性。

1、互斥鎖lock()語句

同步訪問共享資源的首選技術是C#的 lock 關鍵字,lock 允許定義一段線程同步的代碼語句,它需要定義一個標記(即一個對象引用),線程在進入鎖定范圍的時候必須獲得這個標記,在退出鎖定范圍時需要釋放鎖。當試圖鎖定的是一個實例級的私有方法時,使用方法本身所在對象的引用就可以了。然而,如需鎖定公共成員中的一段代碼,比較安全的做法是聲明私有的object成員作為鎖標識。

public class DemoClass
{
    private readonly object threadLock = new object();

    public void Method()
    {
        // 使用鎖標識
        lock (threadLock)
        {
            //……
        }
    }
}

再來一個混合線程同步鎖的例子:

using System;
using System.Threading;

namespace Threading
{
    public sealed class SimpleHybirdLock : IDisposable
    {
        //Int32由基元用戶模式構造(Interlocked的方法)使用
        private Int32 m_waiters = 0;
        // AutoResetEvent是基元內核模式構造
        private AutoResetEvent m_waiterLock = new AutoResetEvent(false);

        public void Enter()
        {
            //指出這個線程想要獲得的鎖
            if (Interlocked.Increment(ref m_waiters) == 1)
                return; //鎖可以自由使用

            //另一個線程擁有鎖(發生競爭),使這個線程等待
            m_waiterLock.WaitOne(); //這里產生較大的性能
            //WaitOne 返回后,這個線程拿到鎖了
        }

        public void Leave()
        {
            //這個線程准備釋放鎖
            if (Interlocked.Increment(ref m_waiters) == 0)
                return; //沒有其他線程等待直接返回

            //有其他線程正在阻塞,喚醒其中一個
            m_waiterLock.Set(); //這里產生較大的性能
            //WaitOne 返回后,這個線程拿到鎖了
        }

        public void Dispose()
        {
            m_waiterLock.Dispose();
        }
    }
}

 

2、Monitor實現線程同步

通過Monitor.Enter() 和 Monitor.Exit()實現排它鎖的獲取和釋放,獲取之后獨占資源,不允許其他線程訪問。

還有一個TryEnter方法,請求不到資源時不會阻塞等待,可以設置超時時間,獲取不到直接返回false。

public class DemoClass
{
    private readonly object threadLock = new object();

    public void Method()
    {
        Monitor.Enter(threadLock);
        try
        {
            //……
        }
        finally
        {
            Monitor.Exit(threadLock);
        }
    }
}

3、維護自由鎖(System.Threading.Interlocked)實現線程同步,Interlocked允許我們對數據進行一些原子操作:CompareExchange(), Decrement(), Exchange(), Increment()。這些靜態方法需要以引用方式傳入變量。如:注意newVal 和 intVal 的值都是遞增之后的值。

4、[Synchronization]特性可以有效地使對象的所以實例的成員都保持線程安全。當CLR分配帶[Synchronization]特性的對象時,它會把這個對象放在同步上下文中。這是編寫線程安全代碼的一種偷懶方式,因為它不需要我們實際深入線程控制敏感數據的細節,但這種方式對性能有影響,因為即使一個方法沒有使用共享資源,CLR仍然會鎖定對該方法的調用。

5、系統內置對象

互斥(Mutex), 信號量(Semaphore), 事件(AutoResetEvent/ManualResetEvent),線程池

https://www.onlinebuff.com/article_understand-monitor-vs-mutex-vs-semaphore-vs-semaphoreslim-onlinebuff_60.html

6、SpinLock

https://docs.microsoft.com/zh-cn/dotnet/standard/threading/how-to-use-spinlock-for-low-level-synchronization

7、輕量級信號量 CountdownEvent,SemaphoreSlim,ManualResetEventSlim

四、線程優先級

系統會為每一個線程分配一個優先級別。.NET線程優先級,是指定一個線程的相對於其他線程的相對優先級,它規定了線程的執行順序,對於在CLR中創建的線程,其優先級別默認為Normal,而在CLR之外創建的線程進入CLR時,將會保留其先前的優先級,可以通過訪問線程的Priority屬性來獲取或設置線程的優先級別。

System.Threading命名空間中的ThreadPriority枚舉定義了一組線程優先級的所有可能值,我這里按級別由高到低排列出來常用的,具體的說明就不在這里解釋。

Highest  , AboveNormal ,  Normal  ,  BelowNormal ,  Lowest

除了這些還有Realtime,但Realtime優先級盡量避免,他的優先級相當高,甚至會干擾操作系統的任務,比如阻礙一些必要的磁盤I/O和網絡傳輸。也可能會造成不及時處理鍵盤和鼠標的輸入,導致用戶會感覺死機了。

 

代碼示例:

using System;
using System.Threading;

namespace Threading
{
    class Program
    {
        public static void Thread1()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.Write("1 ");
            }

        }
        public static void Thread2()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.Write("2 ");
            }
        }
        public static void Thread3()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.Write("3 ");
            }
        }
        public static void Thread4()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.Write("4 ");
            }
        }
        public static void Thread5()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.Write("5 ");
            }
        }
        static void Main(string[] args)
        {
            var t1 = new Thread(Thread1);
            var t2 = new Thread(Thread2);
            var t3 = new Thread(Thread3);
            var t4 = new Thread(Thread4);
            var t5 = new Thread(Thread5);

            t1.Priority = ThreadPriority.Highest;
            t2.Priority = ThreadPriority.AboveNormal;
            t3.Priority = ThreadPriority.Normal;
            t4.Priority = ThreadPriority.BelowNormal;
            t5.Priority = ThreadPriority.Lowest;


            t1.Start();
            t2.Start();
            t3.Start();
            t4.Start();
            t5.Start();

            Console.ReadKey();
        }
    }
}

運行結果:

 很明顯,根據線程的優先級高低順序執行的。

五、阻塞調用線程

Join阻塞調用線程,直到該線程終止。

using System;
using System.Threading;

namespace Threading
{
    class Program
    {
        static void Main(string[] args)
        {
            var threadStartA = new ThreadStart(delegate()
            { 
                for (int i = 0; i < 1000000; i++)
                {
                    if (i % 10000 == 0)
                        Console.Write("A");
                }
            });
            var threadA = new Thread(threadStartA);



            var threadStartB = new ThreadStart(delegate()
            {  
                for (int i = 0; i < 500000; i++)
                {
                    if (i % 10000 == 0)
                        Console.Write("B");
                }
                threadA.Join();  //阻塞線程threadB,插入threadA進行執行
                for (int i = 0; i < 500000; i++)
                {
                    if (i % 10000 == 0)
                        Console.Write("B1");
                }
            });
            var threadB = new Thread(threadStartB);

            //啟動線程
            threadA.Start();
            threadB.Start();

            Console.ReadKey();
        }
    }
}

運行結果:

從運行結果可以看出:一開始,ThreadA和ThreadB交替執行,當ThreadB執行到ThreadA.Join()方法時,ThreadB被阻塞,ThreadA插入進來單獨執行,當ThreadA執行完畢以后,ThreadB繼續執行。

除了ThreadA和ThreadB外,程序中還有一個主線程(Main Thread)。現在我們在主線程中添加一些輸出代碼,看看主線程和工作線程A、B是如何並發運行的。

六、Parallel

這個類提供了For,Foreach,Invoke靜態方法。它內部封裝了Task類。主要用於並行計算。

        private void ParallelTest2()
        {
            for (int i = 1; i < 5; i++)
            {
                Console.WriteLine(DoWork(i));
            }
            var plr = Parallel.For(1, 5, i => Console.WriteLine(DoWork(i)));
        }

        private int DoWork(int num)
        {
            int sum = 0;
            for (int i = 0; i <= num; i++)
            {
                sum += i;
            }
            return sum;
        }

 

//並行的for循環
        static void loop3(List<entityA> source)
        {
            int count = source.Count();
            Parallel.For(0, count, item =>
            {
                //source[count].age= source[count].age + 10;
                System.Threading.Thread.Sleep(10);
            });
        }
 
        //並行的foreach循環
        static void loop4(List<entityA> source)
        {
            Parallel.ForEach(source, item =>
            {
                item.age = item.age + 10;
                System.Threading.Thread.Sleep(10);
            });
        }

  

Parallel.ForEach<DataRow>(dt.Select(), row =>
{
..todo
}

 


免責聲明!

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



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