iOS多線程

線程的定義:

線程是進(jìn)程的基本執(zhí)行單元,一個進(jìn)程的所有任務(wù)都在線程中執(zhí)行,程序啟動會默認(rèn)開啟一條線程,這條線程被稱為主線程或 UI 線程。

通常線程用于處理哪些耗時的操作,來緩解主線程的壓力。

線程和進(jìn)程是有很大區(qū)別的:
1、同一進(jìn)程的線程共享本進(jìn)程的地址空間,而進(jìn)程之間則是獨立的地址空間。
2、同一進(jìn)程內(nèi)的線程共享本進(jìn)程的資源如內(nèi)存、I/O、cpu等,但是進(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ù)共享
多線程的原理

cpu具有調(diào)度的能力,可以調(diào)度該進(jìn)程內(nèi)的線程,在很小的時間片內(nèi)切換線程;

但是只有多核cpu才能來回切換處理線程,單核的話一次只能處理一個線程。

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

多線程同時執(zhí)行:

  • 是 CPU 快速的在多個線程之間的切換
  • CPU 調(diào)度線程的時間足夠快,就造成了多線程的“同時”執(zhí)行的效果
    如果線程數(shù)非常多
  • CPU 會在 N 個線程之間切換,消耗大量的 CPU 資源
  • 每個線程被調(diào)度的次數(shù)會降低,線程的執(zhí)行效率降低
線程的生命周期:
iShot2020-10-31 14.46.20.png

線程池:


iShot2020-10-31 14.51.15.png
線程的優(yōu)先級:

線程的優(yōu)先級threadPriority是一個double類型,它被一個NSQualityOfService所取代,字面意思是服務(wù)質(zhì)量。
NSQualityOfService有5個類型:

typedef NS_ENUM(NSInteger, NSQualityOfService) {
    NSQualityOfServiceUserInteractive = 0x21,   //用戶交互,優(yōu)先級最高
    NSQualityOfServiceUserInitiated = 0x19,     //初始化,第二
    NSQualityOfServiceUtility = 0x11,           //使用工具,第三
    NSQualityOfServiceBackground = 0x09,        //后臺,第四
    NSQualityOfServiceDefault = -1              //默認(rèn)情況,第五
} API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));

優(yōu)先級越高,并不是執(zhí)行越快,它還要看資源大?。ㄈ蝿?wù)復(fù)雜度)和CPU的調(diào)度。

1、原子鎖atomic和非原子鎖nonatomic
atomic:線程安全,需要消耗大量的資源
nonatomic:非線程安全,適合內(nèi)存小的移動設(shè)備

iOS開發(fā)的建議:

  • 所有屬性都聲明為 nonatomic
  • 盡量避免多線程搶奪同一塊資源
  • 盡量將加鎖、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理,減小移動客戶端的壓力

互斥鎖:

當(dāng)發(fā)現(xiàn)其他線程執(zhí)行,當(dāng)前線程休眠(就緒狀態(tài)),一直在等打開喚醒執(zhí)行

互斥鎖小節(jié):

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

自旋鎖:
當(dāng)發(fā)現(xiàn)其他線程執(zhí)行,當(dāng)前線程詢問(一直詢問),一直等待,耗費(fèi)性能比較高。

所以說,當(dāng)需求所需時間短,可以使用自旋鎖,但是比較耗時的,一般使用互斥鎖。

線程通訊

線程通訊通常使用的方法:

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
    // equivalent to the first method with kCFRunLoopCommonModes

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
    // equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

這些方法功能基本上沒啥好說的,從字面意思上就可以理解,下面通過部分代碼來演示一下:

- (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;
}

還有一個點就是關(guān)于NSPort,這個在通常開發(fā)中基本不同,只說一個值得注意的點,當(dāng)需要NSPort執(zhí)行事件時,需要將NSPort加入到Runloop

線程與Runloop的關(guān)系

1:runloop與線程是一一對應(yīng)的,一個runloop對應(yīng)一個核心的線程,為什么說是核心 的,是因為runloop是可以嵌套的,但是核心的只能有一個,他們的關(guān)系保存在一個全局 的字典里。
2:runloop是來管理線程的,當(dāng)線程的runloop被開啟后,線程會在執(zhí)行完任務(wù)后進(jìn)入休 眠狀態(tài),有了任務(wù)就會被喚醒去執(zhí)行任務(wù)。
3:runloop在第一次獲取時被創(chuàng)建,在線程結(jié)束時被銷毀。
4:對于主線程來說,runloop在程序一啟動就默認(rèn)創(chuàng)建好了。
5:對于子線程來說,runloop是懶加載的,只有當(dāng)我們使用的時候才會創(chuàng)建,所以在子線 程用定時器要注意:確保子線程的runloop被創(chuàng)建,不然定時器不會回調(diào)。

關(guān)于iOS線程的一些基本的使用和概念就結(jié)束了;
GCD的使用和一些底層將在下一篇講述。

?著作權(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ù)。

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