iOS-底層原理22-多線程

《iOS底層原理文章匯總》

1.進(jìn)程和線程

  • 線程是進(jìn)程的基本執(zhí)行單元,一個進(jìn)程的所有任務(wù)都在線程中執(zhí)行 * 進(jìn)程要想執(zhí)行任務(wù),必須得有線程,進(jìn)程至少要有一條線程
  • 程序啟動會默認(rèn)開啟一條線程,這條線程被稱為主線程或 UI 線程
  • 進(jìn)程是指在系統(tǒng)中正在運行的一個應(yīng)用程序
  • 每個進(jìn)程之間是獨立的,每個進(jìn)程均運行在其專用的且受保護(hù)的內(nèi)存空間內(nèi)
  • 通過“活動監(jiān)視器”可以查看 Mac 系統(tǒng)中所開啟的進(jìn)程

地址空間:同一進(jìn)程的線程共享本進(jìn)程的地址空間,而進(jìn)程之間則是獨立的地址空間。 資源擁有:同一進(jìn)程內(nèi)的線程共享本進(jìn)程的資源如內(nèi)存、I/O、cpu等,但是進(jìn)程之間的 資源是獨立的。

1: 一個進(jìn)程崩潰后,在保護(hù)模式下不會對其他進(jìn)程產(chǎn)生影響,但是一個線程崩潰整個進(jìn) 程都死掉。所以多進(jìn)程要比多線程健壯。
2: 進(jìn)程切換時,消耗的資源大,效率高。所以涉及到頻繁的切換時,使用線程要好于進(jìn) 程。同樣如果要求同時進(jìn)行并且又要共享某些變量的并發(fā)操作,只能用線程不能用進(jìn)程 3: 執(zhí)行過程:每個獨立的進(jìn)程有一個程序運行的入口、順序執(zhí)行序列和程序入口。但是 線程不能獨立執(zhí)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個線程執(zhí)行控制。
4: 線程是處理器調(diào)度的基本單位,但是進(jìn)程不是。 5: 線程沒有地址空間,線程包含在進(jìn)程地址空間中

  • 優(yōu)點
  • 能適當(dāng)提高程序的執(zhí)行效率
  • 能適當(dāng)提高資源的利用率(CPU,內(nèi)存)
  • 線程上的任務(wù)執(zhí)行完成后,線程會自動銷毀
  • 缺點
  • 開啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,每一個線程都占 512 KB)
  • 如果開啟大量的線程,會占用大量的內(nèi)存空間,降低程序的性能
  • 線程越多,CPU 在調(diào)用線程上的開銷就越大
  • 程序設(shè)計更加復(fù)雜,比如線程間的通信、多線程的數(shù)據(jù)共享
/**
     pthread_create 創(chuàng)建線程
     參數(shù):
     1. pthread_t:要創(chuàng)建線程的結(jié)構(gòu)體指針,通常開發(fā)的時候,如果遇到 C 語言的結(jié)構(gòu)體,類型后綴 `_t / Ref` 結(jié)尾
     同時不需要 `*`
     2. 線程的屬性,nil(空對象 - OC 使用的) / NULL(空地址,0 C 使用的)
     3. 線程要執(zhí)行的`函數(shù)地址`
     void *: 返回類型,表示指向任意對象的指針,和 OC 中的 id 類似
     (*): 函數(shù)名
     (void *): 參數(shù)類型,void *
     4. 傳遞給第三個參數(shù)(函數(shù))的`參數(shù)`
     
     返回值:C 語言框架中非常常見
     int
     0          創(chuàng)建線程成功!成功只有一種可能
     非 0       創(chuàng)建線程失敗的錯誤碼,失敗有多種可能!
     */
    
    // 1: pthread
    pthread_t threadId = NULL;
    //c字符串
    char *cString = "HelloCode";
//    NSString *ocString = @"Gavin";
    //延伸到: OC--C的混編 尤其在智能家居,SDK封裝
    //拋出一個問題: 在ARC需要這樣操作,在MRC不需要
    // OC prethread -- 跨平臺
    // 鎖
    int result = pthread_create(&threadId, NULL, pthreadTest, cString);
    if (result == 0) {
        NSLog(@"成功");
    } else {
        NSLog(@"失敗");
    }
    // 2: NSThread
    [NSThread detachNewThreadSelector:@selector(threadTest) toTarget:self withObject:nil];
    // 3: GCD
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self threadTest];
    });
    
    // 4: NSOperation
    [[[NSOperationQueue alloc] init] addOperationWithBlock:^{
        [self threadTest];
    }];
    
/**
 1. 循環(huán)的執(zhí)行速度很快
 2. 棧區(qū)/常量區(qū)的內(nèi)存操作也挺快
 3. 堆區(qū)的內(nèi)存操作有點慢
 4. I(Input輸入) / O(Output 輸出) 操作的速度是最慢的!
 * 會嚴(yán)重的造成界面的卡頓,影響用戶體驗!
 * 多線程:開啟一條線程,將耗時的操作放在新的線程中執(zhí)行
 */
- (void)threadTest{
    NSLog(@"begin");
    NSInteger count = 1000 * 100;
    for (NSInteger i = 0; i < count; i++) {
        // 棧區(qū)
        NSInteger num = i;
        // 常量區(qū)
        NSString *name = @"zhang";
        // 堆區(qū)
        NSString *myName = [NSString stringWithFormat:@"%@ - %zd", name, num];
        NSLog(@"%@", myName);
    }
    NSLog(@"over");
}

void *pthreadTest(void *para){
    // 接 C 語言的字符串
    //    NSLog(@"===> %@ %s", [NSThread currentThread], para);
    // __bridge 將 C 語言的類型橋接到 OC 的類型
    NSString *name = (__bridge NSString *)(para);
    
    NSLog(@"===>%@ %@", [NSThread currentThread], name);
    
    return NULL;
}

單核CPU,在內(nèi)存中的多線程是快速切換,用戶無法感知到切換


image.png

時間片的概念:CPU在多個任務(wù)直接進(jìn)行快速的切換,這個時間間隔就是時間片

  • (單核CPU)同一時間,CPU 只能處理 1 個線程 * 換言之,同一時間只有 1 個線程在執(zhí)行
  • 多線程同時執(zhí)行:
  • 是 CPU 快速的在多個線程之間的切換
  • CPU 調(diào)度線程的時間足夠快,就造成了多線程的“同時”執(zhí)行的效果
  • 如果線程數(shù)非常多
  • CPU 會在 N 個線程之間切換,消耗大量的 CPU 資源 * 每個線程被調(diào)度的次數(shù)會降低,線程的執(zhí)行效率降低
    多核CPU,才是多個線程同時執(zhí)行

2.線程屬性,命名name

image.png

3.線程生命周期

image.png

線程池


image.png

飽和策略


image.png

image.png

線程中的所有任務(wù)都能cancel掉嗎?正在執(zhí)行的任務(wù)無法cancel,正在執(zhí)行的任務(wù)已經(jīng)被調(diào)度上去了
image.png

4.threadPriority優(yōu)先級qualityOfService,優(yōu)先級越高表明執(zhí)行速度越快,還與任務(wù)的復(fù)雜度有關(guān)

5.多任務(wù)在一條線程,會有資源搶奪的問題

  • 1.買票問題加一把鎖:@synchronized(self),同樣可以加NSLock,信號量,柵欄函數(shù),同步


    image.png

    互斥鎖小結(jié)
    保證鎖內(nèi)的代碼,同一時間,只有一條線程能夠執(zhí)行!
    互斥鎖的鎖定范圍,應(yīng)該盡量小,鎖定范圍越大,效率越差!
    互斥鎖參數(shù),能夠加鎖的任意 NSObject 對象
    注意:鎖對象一定要保證所有的線程都能夠訪問
    如果代碼中只有一個地方需要加鎖,大多都使用 self,這樣可以避免單獨再建一個鎖對象

  • 2.atomic和nonatomic
    nonatomic 非原子屬性
    atomic 原子屬性(線程安全),針對多線程設(shè)計的,默認(rèn)值
    保證同一時間只有一個線程能夠?qū)懭?但是同一個時間多個線程都可以取值) atomic 本身就有一把鎖(自旋鎖) 單寫多讀:單個線程寫入,多個線程可以讀取
    atomic:線程安全,需要消耗大量的資源 nonatomic:非線程安全,適合內(nèi)存小的移動設(shè)備
    iOS 開發(fā)的建議
    所有屬性都聲明為 nonatomic
    盡量避免多線程搶奪同一塊資源 盡量將加鎖、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理,減小移動客戶端的壓力


    image.png
  • 3.自旋鎖和互斥鎖
    互斥鎖:發(fā)現(xiàn)其他線程執(zhí)行,當(dāng)前線程 休眠(就緒狀態(tài)) 一直在等打開 喚醒執(zhí)行
    自旋鎖:發(fā)現(xiàn)其他線程執(zhí)行,當(dāng)前線程 詢問 - 忙等 耗費性能比較高
    短小用自旋鎖
    任務(wù)復(fù)雜度比較高用互斥鎖

6.線程間通訊

- (void)viewDidLoad {
    [super viewDidLoad];

    _imageView = [[UIImageView alloc] init];
    
    // 1. 準(zhǔn)備 URL
    NSString *urlString = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1529488045873&di=7c1cc40bec638406ef48717386a08094&imgtype=0&src=http%3A%2F%2Fpic2.ooopic.com%2F12%2F46%2F41%2F95bOOOPIC74_1024.jpg";
    NSURL *url = [NSURL URLWithString:urlString];

    // 2. 下載圖像
    //[self downloadImageWithURL:url];
    // 異步下載圖像
    [self performSelectorInBackground:@selector(downloadImageWithURL:) withObject:url];

}

/**
 * 下載 URL 指定的網(wǎng)絡(luò)圖片
 */
- (void)downloadImageWithURL:(NSURL *)url {
    
    NSLog(@"%@", [NSThread currentThread]);
    
    // 1. 所有從網(wǎng)絡(luò)返回的都是二進(jìn)制數(shù)據(jù)
    NSData *data = [NSData dataWithContentsOfURL:url];
    
    // 2. 將二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成 image
    UIImage *image = [UIImage imageWithData:data];
    
    // 3. 在主線程更新 UI
    // waitUntilDone: 是否等待 updateImage: 執(zhí)行完成
    [self performSelectorOnMainThread:@selector(updateImage:) withObject:image waitUntilDone:YES];
    
    NSLog(@"完成");
}
/**
 * 更新圖像 UI
 */
- (void)updateImage:(UIImage *)image {
    
    NSLog(@"更新 UI -> %@", [NSThread currentThread]);
    
    // 3. 設(shè)置圖像
    _imageView.image = image;
    
    // 4. 調(diào)整大小
    [_imageView sizeToFit];
    
    // 5. 設(shè)置 contentSize
    _scrollView.contentSize = image.size;
}

7.端口間通信

把port加入runloop,[[NSRunLoop currentRunLoop] addPort:self.myPort forMode:NSDefaultRunLoopMode],接收port消息的端口加入runloop里面


image.png

image.png

8.UIImageView用weak修飾

weak修飾ImageView,一創(chuàng)建就釋放了,棧區(qū)的空間系統(tǒng)自動管理回收,無法進(jìn)行添加,可以通過添加中間變量進(jìn)行賦值,引用計數(shù)進(jìn)行傳遞,在函數(shù)viewDidLoad中都不會被銷毀


image.png

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • iOS 底層原理 文章匯總[http://www.itdecent.cn/p/412b20d9a0f6] 本文的...
    Style_月月閱讀 3,441評論 2 14
  • 本文的目的在于了解進(jìn)程、線程、多線程、線程池等的基本概念及原理 1 線程 和 進(jìn)程 1.1 線程 和 進(jìn)程的定義 ...
    AcmenL閱讀 276評論 0 1
  • 線程的定義 ? 線程是進(jìn)程的基本執(zhí)行單元,一個進(jìn)程的所有任務(wù)都在線程中執(zhí)行? 進(jìn)程要想執(zhí)行任務(wù),必須得有線程,進(jìn)程...
    yan0_0閱讀 697評論 0 3
  • 章前回顧 上章我們了解了鎖的一些知識,線程安全需要鎖的協(xié)助。這章我們探索一下多線程原理篇; 初識 周知,了解多線程...
    孜孜不倦_閑閱讀 607評論 0 0
  • 夜鶯2517閱讀 128,120評論 1 9

友情鏈接更多精彩內(nèi)容