首先說下多線程出現的原因:
為了解決負載均衡問題,充分利用CPU資源.為了提高CPU的使用率,采用多線程的方式去同時完成幾件事情而不互相干擾.為了處理大量的IO操作時或處理的情況需要花費大量的時間等等,比如:讀寫文件,視頻圖像的采集,處理,顯示,保存等
多線程的好處:
1.使用線程可以把占據時間長的程序中的任務放到后台去處理
2.用戶界面更加吸引人,這樣比如用戶點擊了一個按鈕去觸發某件事件的處理,可以彈出一個進度條來顯示處理的進度
3.程序的運行效率可能會提高
4.在一些等待的任務實現上如用戶輸入,文件讀取和網絡收發數據等,線程就比較有用了.
多線程的缺點:
1.如果有大量的線程,會影響性能,因為操作系統需要在它們之間切換.
2.更多的線程需要更多的內存空間
3.線程中止需要考慮對程序運行的影響.
4.通常塊模型數據是在多個線程間共享的,需要防止線程死鎖情況的發生
實現多線程的方法:
1.NSObject
2.NSthread
3.NSOperation
4.GCD
iOS中多線程的方法主要有三種:
1.NSThread 2.NSOperation 3.GCD
(1)NSOperation與GCD的區別:
Mac OS X 10.6及iOS4.0之后導入了可以使全體線程更高效運行,並且使並行處理應用更易開發的架構,GCD(Grand Central Dispatch),同時引入的還有Run Loop,線程(包括Cocoa和POSIX)和 Operation。GCD擁有非常輕量級的工作單元和並發方式,並且由系統決定其最佳調度方式。這個時候出現了一個問題,NSOperation如何處理呢?
其實我們在通過NSOperation和GCD進行開發過程中,會發現兩者執行的方式有許多相似之處,NSOperation和GCD參照對比,NSOperationQueue和dispatch_queue參照對比,但是兩者之間還是有許多差別的,具體區別:
1. GCD的核心是C語言寫的系統服務,執行和操作簡單高效,因此NSOperation底層也通過GCD實現,換個說法就是NSOperation是對GCD更高層次的抽象,這是他們之間最本質的區別.因此如果希望自定義任務,建議使用NSOperation;
2.依賴關系,NSOperation可以設置兩個NSOperation之間的依賴,第二個任務依賴於第一個任務完成執行,GCD無法設置依賴關系,不過可以通過dispatch_barrier_async來實現這種效果;
3.KVO(鍵值對觀察),NSOperation和容易判斷Operation當前的狀態(是否執行,是否取消),對此GCD無法通過KVO進行判斷;
4.優先級,NSOperation可以設置自身的優先級,但是優先級高的不一定先執行,GCD只能設置隊列的優先級,無法在執行的block設置優先級;
5.繼承,NSOperation是一個抽象類實際開發中常用的兩個類是NSInvocationOperation和NSBlockOperation,同樣我們可以自定義NSOperation,GCD執行任務可以自由組裝,沒有繼承那么高的代碼復用度;
6.效率,直接使用GCD效率確實會更高效,NSOperation會多一點開銷,但是通過NSOperation可以獲得依賴,優先級,繼承,鍵值對觀察這些優勢,相對於多的那么一點開銷確實很划算,魚和熊掌不可得兼,取舍在於開發者自己;
關於主要的區別都已經總結,根據實際開發中來說,GCD使用情況較多,簡單高效,從變成原則上來看,應該是使用高層次的抽象,避免使用低層次的抽象,那么無疑我們應該選擇NSOperation,因為復雜的任務可以自己通過NSOperation實現,日常還是GCD的天下,畢竟GCD有更高的並發和執行能力
(2)GCD與NSThread的區別:
1)NSThread通過@selector指定要執行的方法,代碼分散
2)GCD通過Block指定要執行的代碼,代碼集中,所有的代碼寫在一起的,讓代碼更加簡單,易於閱讀和維護
3)使用GCD不需要管理線程的創建/銷毀/復用的過程!程序員不用關心線程的生命周期
4)如果要開多個線程NSThead 必須實例化多個線程對象
5)NSThread靠NSObject的分類方法實現的線程間通訊
一、使用線程的理由
1、可以使用線程將代碼同其他代碼隔離,提高應用程序的可靠性。
2、可以使用線程來簡化編碼。
3、可以使用線程來實現並發執行。
二、基本知識
1、進程與線程:進程作為操作系統執行程序的基本單位,擁有應用程序的資源,進程包含線程,進程的資源被線程共享,線程不擁有資源。
2、前台線程和后台線程:通過Thread類新建線程默認為前台線程。當所有前台線程關閉時,所有的后台線程也會被直接終止,不會拋出異常。
3、掛起(Suspend)和喚醒(Resume):由於線程的執行順序和程序的執行情況不可預知,所以使用掛起和喚醒容易發生死鎖的情況,在實際應用中應該盡量少用。
4、阻塞線程:Join,阻塞調用線程,直到該線程終止。
5、終止線程:Abort:拋出 ThreadAbortException 異常讓線程終止,終止后的線程不可喚醒。Interrupt:拋出 ThreadInterruptException 異常讓線程終止,通過捕獲異常可以繼續執行。
6、線程優先級:AboveNormal BelowNormal Highest Lowest Normal,默認為Normal。
三、線程的使用
線程函數通過委托傳遞,可以不帶參數,也可以帶參數(只能有一個參數),可以用一個類或結構體封裝參數。
namespace Test { class Program { static void Main(string[] args) { Thread t1 = new Thread(new ThreadStart(TestMethod)); Thread t2 = new Thread(new ParameterizedThreadStart(TestMethod)); t1.IsBackground = true; t2.IsBackground = true; t1.Start(); t2.Start("hello"); Console.ReadKey(); } public static void TestMethod() { Console.WriteLine("不帶參數的線程函數"); } public static void TestMethod(object data) { string datastr = data as string; Console.WriteLine("帶參數的線程函數,參數為:{0}", datastr); } } }
四、線程池
由於線程的創建和銷毀需要耗費一定的開銷,過多的使用線程會造成內存資源的浪費,出於對性能的考慮,於是引入了線程池的概念。線程池維護一個請求隊列,線程池的代碼從隊列提取任務,然后委派給線程池的一個線程執行,線程執行完不會被立即銷毀,這樣既可以在后台執行任務,又可以減少線程創建和銷毀所帶來的開銷。
線程池線程默認為后台線程(IsBackground)。
namespace Test { class Program { static void Main(string[] args) { //將工作項加入到線程池隊列中,這里可以傳遞一個線程參數 ThreadPool.QueueUserWorkItem(TestMethod, "Hello"); Console.ReadKey(); } public static void TestMethod(object data) { string datastr = data as string; Console.WriteLine(datastr); } } }
五、Task類
使用ThreadPool的QueueUserWorkItem()方法發起一次異步的線程執行很簡單,但是該方法最大的問題是沒有一個內建的機制讓你知道操作什么時候完成,有沒有一個內建的機制在操作完成后獲得一個返回值。為此,可以使用System.Threading.Tasks中的Task類。
構造一個Task<TResult>對象,並為泛型TResult參數傳遞一個操作的返回類型。
namespace Test { class Program { static void Main(string[] args) { Task<Int32> t = new Task<Int32>(n => Sum((Int32)n), 1000); t.Start(); t.Wait(); Console.WriteLine(t.Result); Console.ReadKey(); } private static Int32 Sum(Int32 n) { Int32 sum = 0; for (; n > 0; --n) checked{ sum += n;} //結果太大,拋出異常 return sum; } } }
一個任務完成時,自動啟動一個新任務。
一個任務完成后,它可以啟動另一個任務,下面重寫了前面的代碼,不阻塞任何線程。
namespace Test { class Program { static void Main(string[] args) { Task<Int32> t = new Task<Int32>(n => Sum((Int32)n), 1000); t.Start(); //t.Wait(); Task cwt = t.ContinueWith(task => Console.WriteLine("The result is {0}",t.Result)); Console.ReadKey(); } private static Int32 Sum(Int32 n) { Int32 sum = 0; for (; n > 0; --n) checked{ sum += n;} //結果溢出,拋出異常 return sum; } } }
六、委托異步執行
委托的異步調用:BeginInvoke() 和 EndInvoke()
namespace Test { public delegate string MyDelegate(object data); class Program { static void Main(string[] args) { MyDelegate mydelegate = new MyDelegate(TestMethod); IAsyncResult result = mydelegate.BeginInvoke("Thread Param", TestCallback, "Callback Param"); //異步執行完成 string resultstr = mydelegate.EndInvoke(result); } //線程函數 public static string TestMethod(object data) { string datastr = data as string; return datastr; } //異步回調函數 public static void TestCallback(IAsyncResult data) { Console.WriteLine(data.AsyncState); } } }
七、線程同步
1)原子操作(Interlocked):所有方法都是執行一次原子讀取或一次寫入操作。
2)lock()語句:避免鎖定public類型,否則實例將超出代碼控制的范圍,定義private對象來鎖定。
3)Monitor實現線程同步
通過Monitor.Enter() 和 Monitor.Exit()實現排它鎖的獲取和釋放,獲取之后獨占資源,不允許其他線程訪問。
還有一個TryEnter方法,請求不到資源時不會阻塞等待,可以設置超時時間,獲取不到直接返回false。
4)ReaderWriterLock
當對資源操作讀多寫少的時候,為了提高資源的利用率,讓讀操作鎖為共享鎖,多個線程可以並發讀取資源,而寫操作為獨占鎖,只允許一個線程操作。
5)事件(Event)類實現同步
事件類有兩種狀態,終止狀態和非終止狀態,終止狀態時調用WaitOne可以請求成功,通過Set將時間狀態設置為終止狀態。
1)AutoResetEvent(自動重置事件)
2)ManualResetEvent(手動重置事件)
6)信號量(Semaphore)
信號量是由內核對象維護的int變量,為0時,線程阻塞,大於0時解除阻塞,當一個信號量上的等待線程解除阻塞后,信號量計數+1。
線程通過WaitOne將信號量減1,通過Release將信號量加1,使用很簡單。
7)互斥體(Mutex)
獨占資源,用法與Semaphore相似。
8)跨進程間的同步
通過設置同步對象的名稱就可以實現系統級的同步,不同應用程序通過同步對象的名稱識別不同同步對象。