多線程隨筆

  1 ===================
  2 
  3 多線程
  4 
  5 ==================
  6 
  7  
  8 
  9 多線程
 10 
 11 1、概念:
 12 
 13 程序:(Program)(App)是一個能夠運行的文件(咱們寫的代碼)
 14 
 15 進程:(Process)是程序執行的一個操做實體
 16 
 17         即在系統中正在運行的一個應用程序
 18 
 19     每一個進程之間是獨立的,每一個進程均運行在其專用且受保護的內存空間內
 20 
 21     好比同時打開QQ、Xcode,系統就會分別啓動2個進程
 22 
 23     經過「活動監視器」能夠查看Mac系統中所開啓的進程
 24 
 25 01_iOS進程.png
 26 
 27 02_進程和系統.png
 28 
 29 線程:(Thread)線程是進程的基本執行單元,一個進程(程序)的全部任務都在線程中執行    
 30 
 31 一個進程要想執行任務,必須得有線程(每1個進程至少要有1條線程)
 32 
 33     好比使用酷狗播放音樂、使用迅雷下載電影,都須要在線程中執行
 34 
 35 03_進程和線程.png
 36 
 37 2、背景:
 38 
 39     線程的串行
 40 
 41     1個線程中任務的執行是串行的
 42 
 43     若是要在1個線程中執行多個任務,那麼只能一個一個地按順序執行這些任務
 44 
 45     也就是說,在同一時間內,1個線程只能執行1個任務
 46 
 47     好比在1個線程中下載3個文件(分別是文件A、文件B、文件C)
 48 
 49  
 50 
 51 當用戶播放音頻、下載資源、進行圖像處理時每每但願作這些事情的時候其餘操做不會被中斷或者但願這些操做過程當中更加順暢。在單線程中一個線程只能作一件事情,一件事情處理不完另外一件事就不能開始,這樣勢必影響用戶體驗。
 52 
 53 耗時操做
 54 
 55   不依賴邏輯部分的東西
 56 
 57  
 58 
 59 意義:
 60 
 61 隨着移動設備的更新換代,移動設備的性能也不斷提升,如今流行的CPU已經進入雙核、甚至四核時代。如何充分發揮這些CPU的性能,會變得愈來愈重要。在iOS中若是想要充分利用多核心CPU的優點,就要採用併發編程,提升CPU的利用率。
 62 
 63  
 64 
 65 因此這節課學好了,會是面試的亮點
 66 
 67  
 68 
 69 3、多線程
 70 
 71  
 72 
 73 1.多線程原理
 74 
 75 串行並行
 76 
 77 04_線程的串並行.png
 78 
 79  
 80 
 81 同一時間,CPU只能處理1條線程,只有1條線程在工做(執行)
 82 多線程併發(同時)執行,實際上是CPU快速地在多條線程之間調度(切換)
 83 若是CPU調度線程的時間足夠快,就形成了多線程併發執行的假象
 84 思考:若是線程很是很是多,會發生什麼狀況?
 85 CPU會在N多線程之間調度,CPU會累死,消耗大量的CPU資源
 86 每條線程被調度執行的頻次會下降(線程的執行效率下降)
 87 
 88  
 89 
 90 2.多線程的優缺點
 91 
 92  
 93 
 94 多線程的優勢
 95 
 96 能適當提升程序的執行效率
 97 
 98 能適當提升資源利用率(CPU、內存利用率)
 99 
100  
101 
102 多線程的缺點
103 
104 開啓線程須要佔用必定的內存空間(默認狀況下,主線程佔用1M,子線程佔用512KB),若是開啓大量的線程,會佔用大量的內存空間,下降程序的性能
105 
106 線程越多,CPU在調度線程上的開銷就越大
107 
108 程序設計更加複雜:好比線程之間的通訊、多線程的數據共享
109 
110  
111 
112 3.多線程在iOS開發中的應用
113 
114  
115 
116 主線程:一個iOS程序運行後,默認會開啓1條線程,稱爲「主線程」或「UI線程」
117 
118 主線程的主要做用
119 
120 顯示\刷新UI界面
121 
122 處理UI事件(好比點擊事件、滾動事件、拖拽事件等)
123 
124 主線程的使用注意:別將比較耗時的操做放到主線程中。
125 
126 耗時操做會卡住主線程,嚴重影響UI的流暢度,給用戶一種「卡」的壞體驗
127 
128  
129 
130 4.iOS的線程模型
131 
132 pthread(底層C線程庫)
133 
134 NSThread(OC線程庫)
135 
136 NSOperationQueue(線程池/線程隊列)
137 
138 GCD(Blocks模式的線程池)
139 
140 05_iOS線程模型.png
141 
142  
143 
144 4、總體認知結束,詳見代碼
145 
146  
147 
148  
149 
150 01_TimeConsumingAndPhread
151 
152  
153 
154 這個工程告訴咱們:
155 
156     //1.誰來響應咱們的UI操做? 都是主線程響應
157 
158     //2.主線程處理任務,都是按照天然順序處理,誰先來先處理誰,在一件事沒有處理完成以前,這個傢伙不會抽身去處理其餘的事兒
159 
160     //3.currentThread 得到當前邏輯代碼是運行在那一個線程中
161 
162     //4.主線程的number = 1 永遠是 1
163 
164  
165 
166     //請問咱們應該在主線程上執行什麼樣的操做呢?
167 
168     //主線程上執行的操做最好都是及時響應的,不能讓用戶以爲咱們的程序像windows中的程序同樣卡死了,這是一個很嚴重的用戶體驗問題
169 
170  
171 
172     //若是我真的須要完成一個很是耗時間的操做怎麼辦?
173 
174     //1.必定不要在主線程作這件事
175 
176     //2.開啓一條新的線程(子線程),來完成耗時操做
177 
178     //3.常見耗時操做
179 
180     //大量的開模運算(數學類運算),圖形運算,OpenGLES2.0(通常接觸不到)
181 
182     //網絡操做(1.網絡環境影響(2G,2.5G,3G,4G相應速度都不一樣,大數據下載操做))
183 
184    
185 
186             pthread:C方法建立子線程
187 
188     pthread_t cThread;
189 
190     int b = pthread_create(&cThread, NULL, working, NULL);
191 
192 缺點:很差寫,很差控
193 
194 優勢:只是開個線程而已
195 
196  
197 
198  
199 
200  
201 
202 02_NSThreadAndNSLock
203 
204  
205 
206 這個工程告訴咱們:
207 
208 1、
209 
210     //NSThread 是 cocoa(MacOS,iOS)中一個輕量級的多線程對象
211 
212     //NSThread 傻瓜式的操做,簡單
213 
214     //1.如何建立一個子線程
215 
216     //2.把耗時操做的邏輯轉移到子線程中去
217 
218     //object 給 @selector中要執行的方法傳遞參數
219 
220     
221 
222 //    //方法一
223 
224 //    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(doWCing) object:nil];
225 
226 //    //能夠給子線程命名
227 
228 //    thread.name = @"子線程1";
229 
230 //    //使用alloc建立的線程,必定要顯示的調用start方法,纔可以運行
231 
232 //    [thread start];
233 
234     
235 
236     //方法二
237 
238     // 建立一個線程  只是建立就好 不親自執行這個線程 只是建立
239 
240     // 線程建立後 就會自動運行
241 
242     //爲了更加簡化咱們建立一個子線程的操做,NSObject對建立線程封裝了一些方法
243 
244     //內部會自動的建立一個子線程,而且把@selector中的方法交給子線程去作
245 
246     [NSThread detachNewThreadSelector:@selector(doWCing) toTarget:self withObject:nil];
247 
248     
249 
250     //線程工做完程,若是之後再也不使用這個線程
251 
252     //應當作以下操做
253 
254     //    [thread cancel];
255 
256     [NSThread exit];
257 
258  
259 
260 2、
261 
262     //線程鎖 類比上廁所,多個線程有一把鎖就夠了。誰鎖,誰才能打開。
263 
264     NSLock * _threadLock;
265 
266     /**
267 
268      *  (atomic)。所謂原子操做是指不會被線程調度機制打斷的操做;這種操做一旦開始,就一直運行到結束,中間不會有任何 context switch (切換到另外一個線程)。
269 
270      *
271 
272      //經過加鎖和解鎖,使一段代碼成爲原子操做。
273 
274      //使用線程鎖,不是服務於線程,而是服務於代碼。當咱們但願一段代碼進行原子操做,如數據庫的寫入,就能夠進行加鎖。
275 
276      //好比從判斷緩存是否存在,到寫入新的緩存。應當加一把線程鎖。即便咱們沒有建立NSThread。
277 
278      //它和@Syncronized關鍵字起到一樣效果。可是關鍵字不能在一個函數中加鎖,另外一個函數中解鎖,NSLock能夠。
279 
280      */
281 
282     //上鎖
283 
284     [_threadLock lock];
285 
286     //解鎖
287 
288     [_threadLock unlock];
289 
290  
291 
292 3、
293 
294     //NSThread 很難人爲的去控制建立子線程的數量
295 
296     //子線程建立過多,會嚴重的影響程序的性能,由於每個線程都會佔用必定cpu的資源,致使cpu很忙碌,特別是多人開發的時候
297 
298  
299 
300  
301 
302 03_GCDDemo
303 
304  
305 
306 這個工程告訴咱們:
307 
308  
309 
310 1.什麼是GCD?
311 
312 爲併發代碼在多核硬件(ios(iphone4s),mac os)上高效執行多線程代碼而生的技術
313 
314 彌補了NSThread難於管理的問題
315 
316 //Grand Central Dispatch(多線程優化技術)或稱GCD,是一套底層API,提供了新的模式編寫併發執行的程序。容許將一個程序切分爲多個單一任務,而後提交到工做隊列並併發地或者串行地執行。
317 
318  
319 
320 2.什麼是Queue隊列?
321 
322 GCD使用了隊列概念,已經很好的解決了NSThread難於管理的問題,隊列實際上就是一個數組的概念,一般咱們把要執行的任務,放到一個隊列中去管理
323 
324 //GCD採用隊列的方式管理線程,不只能夠管理多線程,併發的任務,設置主線程,也經過任務隊列的方式進行管理。
325 
326 //因此說GCD的隊列是任務隊列,而不是線程隊列。
327 
328  
329 
330 3.什麼是串行隊列?
331 
332 依次完成每個任務
333 
334  
335 
336 4.什麼是並行隊列?
337 
338 好像全部的任務都是在同一時間執行的
339 
340  
341 
342 5.mainqueue(主隊列)串行隊列,全局隊列(Global Queue),本身建立隊列
343 
344  
345 
346  
347 
348 CGD存在的問題
349 
350 1.純c的代碼,讓不少面向對象程序員頭疼
351 
352 2.雖然已經很好的解決了NSThread難於管理的問題,可是還不是很好,當程序在某一個時間點上若是須要執行不少個好時操做,也會出現建立不少個線程的問題,致使系統性能出現問題
353 
354  
355 
356 優勢:
357 
358 經過GCD,開發者不用再直接跟線程打交道了,只須要向隊列中添加block代碼便可,GCD在後端管理着一個線程池。GCD不只決定着哪一個線程(block)將被執行,它還根據可用的系統資源對線程池中的線程進行管理——這樣能夠不經過開發者來集中管理線程,緩解大量線程的建立,作到了讓開發者遠離線程的管理。
359 
360 缺點:
361 
362 雖然GCD是稍微偏底層的一個API,可是使用起來很是的簡單。不過這也容易使開發者忘記併發編程中的許多注意事項和陷阱。
363 
364  
365 
366  
367 
368  
369 
370 1、Dispatch Queues
371 
372  
373 
374     1.用戶隊列(用戶隊列,串行執行):
375 
376     用戶隊列(GCD中對這種隊列沒有特定的名詞來描述,姑且如此稱之)可使用dispatch_queue_create函數建立隊列。這些隊列是串行執行的。
377 
378     //怎麼使用呢?
379 
380     //想要執行GCD的方法dispatch_async 必須把它交個一個隊列管理,不然報錯
381 
382     //dispatch_async 開一個子線程,不會阻塞主線程的操做,操做對象是線程,而不是方法
383 
384     //向隊列中添加block任務
385 
386     //這是能夠選擇任務是同步執行仍是異步執行
387 
388     //dispatch_async 函數會將傳入的block塊放入指定的queue裏運行。這個函數是異步的,這就意味着它會當即返回而無論block是否運行結束。
389 
390     //同步,失去了多線程的意義,只不過是拿出來放在本身的隊列裏而已
391 
392     
393 
394     //把任務都添加在了同一隊列(線程)queue上
395 
396     //因此本身建立的隊列是串行的
397 
398  
399 
400  
401 
402  
403 
404     2.Global queues(全局隊列,並行執行):
405 
406     全局隊列是併發隊列,並由整個進程共享。進程中存在三個全局隊列,能夠調用dispatch_get_global_queue函數,傳入優先級來訪問隊列。
407 
408  
409 
410     抓取全局的隊列
411 
412     global_queue並行隊列
413 
414     能夠同時運行多個任務,每一個任務的啓動時間是按照加入queue的順序,即任務開始執行的順序和其加入隊列的順序相同,結束的順序依賴各自的任務.咱們本身不能去建立並行調度隊列。只有三個可用的global concurrent queues。使用dispatch_get_global_queue得到
415 
416     //因爲GCD中同一個隊列中的任務是串行執行的。因此只要將那些須要線程保護的任務添加到同一個隊列當中,就能實現串行執行。
417 
418  
419 
420  
421 
422 3.The main queue(主隊列,串行執行):
423 
424     提交到main queue的任務將在主線程執行。main queue能夠調用dispatch_get_main_queue()來得到。由於main queue是與主線程相關的,因此這是一個串行隊列。
425 
426     //至少兩個線程會調用這個函數,若是這個函數中有須要原子操做的代碼,應當加線程鎖,或者使用GCD進行保護
427 
428     
429 
430     //使用GCD保護
431 
432     //將須要保護的代碼交由主線程執行。是主線程,也是主隊列,一個隊列的任務,是串行的。
433 
434 PS:
435 
436 還有一些其餘的東西不須要了解的,工做基本不用,面試就說知道這個東西,可是不經常使用,若是須要能夠細查查
437 
438 講這些只會加大咱們的負擔,影響學習心情和進度,意義不大
439 
440  
441 
442 好比GCD的內存管理
443 
444 好比GCD的線程阻礙等。。。
445 
446  
447 
448  
449 
450  
451 
452 04_GCDMakeSingleton
453 
454  
455 
456 GCD具備如下優勢:
457 
458  
459 
460 GCD 能經過推遲昂貴計算任務並在後臺運行它們來改善你的應用的響應性能。
461 
462 GCD 提供一個易於使用的併發模型而不只僅只是鎖和線程,以幫助咱們避開併發陷阱。
463 
464 GCD 具備在常見模式(例如單例)上用更高性能的原語優化你的代碼的潛在能力。
465 
466  
467 
468  
469 
470 單例模式是開發中最經常使用的寫法之一,建立一個單例不少辦法,iOS的單例模式有兩種官方寫法,以下:
471 
472  
473 
474 不使用GCD
475 
476 #import "ServiceManager.h"
477 
478  
479 
480 static ServiceManager *defaultManager;
481 
482  
483 
484 @implementation ServiceManager
485 
486  
487 
488 +(ServiceManager *)defaultManager{
489 
490     if(!defaultManager)
491 
492     defaultManager=[[self allocWithZone:NULL] init];
493 
494     return  defaultManager;
495 
496 }
497 
498  
499 
500 @end
501 
502  
503 
504 某些特殊狀況下,if執行後面執行的慢了,而後另外一個線程用了他,就會建立,if並不安全
505 
506  
507 
508 使用GCD
509 
510 #import "ServiceManager.h"
511 
512  
513 
514 @implementation ServiceManager
515 
516  
517 
518 +(ServiceManager *)sharedManager{
519 
520     static dispatch_once_t predicate;
521 
522     static ServiceManager * sharedManager;
523 
524     dispatch_once(&predicate, ^{
525 
526     sharedManager=[[ServiceManager alloc] init];
527 
528     });
529 
530     return sharedManager;
531 
532 }
533 
534  
535 
536 @end
537 
538  
539 
540 dispatch_once這個函數,它能夠保證整個應用程序生命週期中某段代碼只被執行一次!
541 
542  
543 
544 1. 線程安全。
545 
546 2. 知足靜態分析器的要求。
547 
548 3. 兼容了ARC
549 
550  
551 
552 咱們看到,該方法的做用就是執行且在整個程序的聲明週期中,僅執行一次某一個block對象。簡直就是爲單例而生的嘛。並且,有些咱們須要在程序開頭初始化的動做,若是爲了保證其,僅執行一次,也能夠放到這個dispatch_once來執行。
553 
554 總結:1.這個方法能夠在建立單例或者某些初始化動做時使用,以保證其惟一性。2.該方法是線程安全的,因此請放心大膽的在子線程中使用。(前提是你的dispatch_once_t *predicate對象必須是全局或者靜態對象。這一點很重要,若是不能保證這一點,也就不能保證該方法只會被執行一次。)
555 
556  
557 
558  
559 
560 //單例建立的寫法
561 
562 http://blog.csdn.net/lovefqing/article/details/8516536
563 
564 //單例建立寫法的緣由
565 
566 http://blog.sina.com.cn/s/blog_4cd8dd130101mi37.html
567 
568  
569 
570  ios 中如何實現一個真正的單利模式
571 
572  一個常見的擔心是它們經常不是線程安全的。這個擔心十分合理,基於它們的用途:單例經常被多個控制器同時訪問。
573 
574  
575 
576  1.首先要保證allocWithZone是線程安全,當中調用super方法的時候使用dispatch_once方法鎖住
577 
578  2.還要保證單利實現方法中也使用了dispatch_once方法鎖住建立對象過程
579 
580  
581 
582         //dispatch_once的做用正如其名:對於某個任務執行一次,且只執行一次。 dispatch_once函數有兩個參數,第一個參數predicate用來保證執行一次,第二個參數是要執行一次的任務block。
583 
584         //dispatch_once被普遍使用在單例、緩存等代碼中,用以保證在初始化時執行一次某任務。
585 
586         //dispatch_once在單線程程序中毫無心義,但在多線程程序中,其低負載、高可依賴性、接口簡單等特性,贏得了廣大消費者的一致五星好評。
587 
588  
589 
590  
591 
592     //(mutableCopy深 與Copy淺,字面理解,能變的是深拷貝)
593 
594     //淺拷貝,就是隻建立一個同類型對象返回
595 
596     //深拷貝就是,不但建立一個同類型對象回去,而且這個對象中全部的屬性值一併都賦值過來
597 
598     //深拷貝和淺拷貝能夠簡單理解爲:若是一個類擁有資源,當這個類的對象發生複製過程的時候,資源從新分配,這個過程就是深拷貝,反之,沒有從新分配資源,就是淺拷貝
599 
600     //copy是建立一個新對象(copy 是內容拷貝),retain是建立一個指針,引用對象計數加1(指針拷貝)
601 
602     //使用區別
603 
604     //mutableCopy獲得一個新的可變對象,能夠看到它的地址和原來對象的地址是不一樣的,也就是新對象的retainCount從0-1。
605 
606     //而copy獲得的是一個不可變對象,這裏分爲兩種狀況:一、若是原來的對象也是一個不可變的,那麼他們的地址指向同一個地址,也就是說它們同一個對象,只是把retainCount加1了而已,二、原來的對象是一個可變對象,那麼它會新生成一個不可變對象,地址不一樣,也是retainCount從0-1。
607 
608  
609 
610  
611 
612 05_NSOperationQueue
613 
614 NSOperation使用方法
615 
616     iOS中有多種多線程實現方式,蘋果公司建議咱們都使用NSOperation技術
617 
618     1.GCD是純c的對於面向對象程序員來講很是不友好
619 
620     2.GCD對線程的管理還不是很強大
621 
622  
623 
624     NSOperation底層實現就是基於GCD來作的
625 
626  
627 
628     dispatch_queue  == NSOperationQueue
629 
630     dispatch_asyn() == NSOperation
631 
632     dispatch_syn()  == NSOperation
633 
634  
635 
636  
637 
638     iOS併發編程中,把每一個併發任務定義爲一個Operation,對應的類名是NSOperation。NSOperation是一個抽象類,沒法直接使用,它只定義了Operation的一些基本方法。咱們須要建立一個繼承於它的子類或者使用系統預約義的子類。目前系統預約義了兩個子類:NSInvocationOperation和NSBlockOperation。
639 
640  
641 
642  
643 
644     NSInvocationOperation
645 
646         NSInvoationOperation是一個基於對象和selector的Operation,使用這個你只須要指定對象以及任務的selector,若是必要,你還能夠設定傳遞的對象參數。
647 
648         NSInvocationOperation *invacationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomethingWithObj:) object:obj];
649 
650  
651 
652         同時當這個Operation完成後,你還能夠獲取Operation中Invation執行後返回的結果對象。
653 
654         id result = [invacationOperation result];
655 
656  
657 
658     NSBlockOperation
659 
660         在一個Block中執行一個任務,這時咱們就須要用到NSBlockOperation。能夠經過blockOperationWithBlock:方法來方便地建立一個NSBlockOperation:
661 
662         NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
663 
664         //Do something here.
665 
666         }];
667 
668  
669 
670     運行一個Operation
671 
672         調用Operation的start方法就能夠直接運行一個Operation。
673 
674         [operation start];
675 
676     取消一個Operation
677 
678         要取消一個Operation,要向Operation對象發送cancel消息:
679 
680         [operation cancel];
681 
682  
683 
684  
685 
686 06_ThreadsCommunication
687 
688 多種主線程的獲取方式(即線程之間的通訊)
689 
690  
691 
692 //經過NSThread
693 
694     //假設是子線程執行該方法,如何回調主線程,修改UI
695 
696     [self performSelectorOnMainThread:@selector(finished) withObject:nil waitUntilDone:NO];
697 
698     //這個方法是NSObject的類別方法,全部對象都能調用。
699 
700     //當前線程回調主線程完成工做,第三個參數是若是傳YES,則當前線程等待主線程完成這一工做後,繼續執行,不然阻塞。若是傳NO,則當前線程不阻塞。
701 
702  
703 
704  
705 
706 //經過operationQueue
707 
708     //線程隊列
709 
710     //經過這個方法找到主隊列,將任務添加給主隊列去完成,便可交付給主線程完成。
711 
712     NSOperationQueue * mainQueue = [NSOperationQueue mainQueue];
713 
714  
715 
716     [mainQueue addOperationWithBlock:^(void){
717 
718     NSLog(@"判斷執行當前block的是不是主線程 %d",[NSThread isMainThread]);
719 
720     }];
721 
722  
723 
724 //經過GCD
725 
726     dispatch_async(dispatch_get_main_queue(), ^(void){
727 
728     NSLog(@"主線程執行這裏的語句");
729 
730     });
731 
732  
View Code
相關文章
相關標籤/搜索