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