多線程學習筆記第3篇
前言:這篇部落客要是在上一篇部落格的基礎上,繼續記錄了死結,線程同步和線程池的一些知識,方便我以後繼續的學習,在這裡貼出來和大家共用一下
1. 對象池技術類比
(1) 如果一些對象建立非常消耗時間和資源,使用非常頻繁,這時候可以考慮使用對象池技術
(2)對象池其實就是一個數組,當我們想用一個對象的時候直接從池子裡面取,別的對象也可以從池子裡面取
(3)使用代碼類比對象池Demo添加鎖
namespace ObjectPoolPractice { class Program { static void Main(string[] args) { //定義一個對象池 MyConnection[] pool = new MyConnection[100]; int index = 0; //當前對象池中對象的個數 //定義一個鎖對象 Object objLock = new object(); //可以鎖定字串,但是盡量不要鎖定字串,因為相同的字串指向的引用相同 //當然也可以鎖定this //類比對象池的使用,生產者和消費者的類比 //類比5個生產者,不停地往對象池裡面添加對象 for (int i = 0; i < 5; i++) { Thread thread = new Thread(() => { while (true) { //鎖的對象必須是參考型別,因為同步索引塊在堆上的 //只有Lock同一個對象才能互斥 lock (objLock)//阻塞當前線程,等待拿到當前對象的鎖 { if (index < pool.Length) { Console.WriteLine("生產一個對象"); //生產對象,並將對象放到池子裡面去,生產者 MyConnection conn = new MyConnection(); //放到池子裡面 pool[index] = conn; index++; }//執行到最後的時候釋放鎖 Thread.Sleep(300); } } }); thread.IsBackground = true; thread.Start(); } //類比10個消費者 for (int i = 0; i < 10; i++) { Thread thread = new Thread(() => { while (true) { lock (objLock) { if (index > 0) { Console.WriteLine("消費一個對象:" + pool[index - 1].ToString()); pool[index - 1] = null; index--; } Thread.Sleep(30); } } }); thread.IsBackground = true; thread.Start(); } Console.ReadLine(); } } //假設建立此對象非常消耗時間 public class MyConnection { } }
(4)資料庫模仿鎖
1)select * from bank with(nolock) --表示當前表做查詢的時候不加鎖
(5)解決死結的方法是操作資源的順序一致
2. 線程同步
(1) 參考上面的案例代碼進行說明
(2)new在堆上申請記憶體空間後幹了什麼
1)Object objLock = new object();
2)開闢記憶體
3)調用建構函式
4)同步塊索引(複數)
(3)Lock(文法糖)
try
{
System.Threading.Monitor.Enter(obj);
}
finally
{
System.Threading.Monitor.Exit(obj);
}
(4)lock這段代碼是怎麼啟動並執行,lock語句根本使用的就是Monitor.Enter和Monitor.Exit,也就是說lock(o)時執行Monitor.Enter(this), 大括弧結束時執行Monitor.Exit(this).他的意義在於什麼呢,對於任何一個對象來說,他在記憶體中的第一部分放置的是所有方法的地址,第二部分放著一個索引,他指向CLR中的SyncBlock Cache地區中的一個SyncBlock.什麼意思呢?就是說,當你執行Monitor.Enter(Object)時,如果object的索引值為負數,就從SyncBlock Cache中選區一個SyncBlock,將其地址放在object的索引中。這樣就完成了以object為標誌的鎖定,其他的線程想再次進行Monitor.Enter(object)操作,將獲得object為正數的索引,然後就等待。直到索引變為負數,即線程使用Monitor.Exit(object)將索引變為負數。
(5)線程同步解決的問題:解決多個線程同時訪問同一個資源的時候,進行同步。同一個時間,我們的資源只允許一個線程來訪問。
3. 線程池
(1) 在程式中,如果某個建立某種對象所需要的代價太高,同時這個對象又可以反覆使用,那麼我們往往就會準備一個容器,用來儲存一批這樣的對象。於是乎,我們想要用這種對象時,就不需要每次去建立一個,而直接從容器中取出一個現成的對象就可以了。由於節省了建立對象的開銷,程式效能自然就上升了
(2)ThreadPool class提供了一個線程池,該線程池可用於發送工作項目、處理非同步 I/O、代表其他線程等待以及處理計時器。 線程池允許在後台運行多個工作,而不需要為每個任務頻繁地建立和銷毀單獨的線程,從而減少了開銷。
(3)普通的Windows應用程式(如控制台或WinForm/WPF),會將其設定為"處理器數 * 250"。也就是說,如果您的機器為2個2核CPU,那麼CLR線程池的容量預設上限便是1000,也就是說,它最多可以管理1000個線程同時運行
(4)WaitCallback是一個委託類型,查看源碼是:
public delegate void WaitCallback(object state);
(5)線程池Demo
static void Main(string[] args) { Console.WriteLine("主線程是:" + Thread.CurrentThread.ManagedThreadId); //直接將一個方法教給線程池,線上程池內部的線程閒置時候自動執行當前傳進去的委託方法 for (int i = 0; i < 1000; i++) { //一般情況下做非同步都要使用線程池,非常特殊的情況,比如你必須拿到當前線程的執行個體的時候,那麼你可以考慮手動建立線程 //.net很多方面都是用線程池,,比如非同步委託,只要是系統內部幫我們分配的線程都是通過線程池裡面提供的線程 ThreadPool.QueueUserWorkItem(new WaitCallback(DemoWork),"韓迎龍"); } Console.ReadLine(); } private static void DemoWork(object state) { //這是由線程池來執行的方法 Console.WriteLine("當前執行的方法的線程是:{0}+{1}", Thread.CurrentThread.ManagedThreadId, state); } //可以執行查看一下,這裡每個人的機器結果可能都不一樣,所以試一下
(6)線程池支援的最大線程數1023個,預設設定是1000,最小0.
static void Main(string[] args) { int maxThread = 0; int currentMaxTrhead = 0; ThreadPool.GetMaxThreads(out maxThread, out currentMaxTrhead); ; Console.WriteLine("最多線程數是:{0},當前的設定是{1}", maxThread, currentMaxTrhead); //看最小的和最大的一摸一樣 Console.ReadKey(); } //執行結果是:最多線程數是:1023,當前的設定是1000
(7)等待線程執行結束
static void Main(string[] args) { int a = 0; Thread thread = new Thread(() => { Thread.Sleep(3000); for (int i = 0; i < 10; i++) { a++; } Console.WriteLine("韓迎龍"); }); thread.IsBackground = true; thread.Start(); Thread.Sleep(1000); thread.Abort(); //告訴作業系統,將這個線程關閉 thread.Join(); //讓當前線程阻塞,停下來等待thread執行完成 Console.WriteLine(a); Console.ReadLine(); }
(8)工作項目
static void Main(string[] args) { //工作項目:比傳統的線程池多了一個傳回值 Task<int> task = new Task<int>(a => { return (int)a; }, 5); task.Start(); //調用工作項目執行 Console.WriteLine(task.Result); //task最好的東西就是能夠擷取到傳回值 }