一、概述
- 程序
程序就是程式在系統中
正在運作
的一個程式。啟動一個App就是開啟了一個程序,為了保證這個程序持續運作下去,至少要有一個線程來支援程式運作。
每個程序之間是獨立的,每個程序均運作在專用且受保護的記憶體空間内。
程序是CPU配置設定資源和排程的機關。
- 線程
一個程序(程式)的所有任務都線上程中執行,線程是CPU執行任務的最小機關。
線程的串行與并行
串行就是執行的任務一個個的按順序執行(羊肉串.jpg),即同一時間内隻能執行一個任務。
并行就是并列(同時)執行,一個線程沒法并列。是以并行至少要兩個線程以上說才有意義。
多線程原理
同一時間内,CPU隻能處理一條線程,隻有一條線程在工作(執行),多線程并發(同時)執行,其實是CPU快速地在多條線程之間切換(排程),造成了多線程并發執行的假象。雙核和多核也不是多線程的它隻不過是指在一個處理器上內建兩個運算核心,進而提高計算能力。
多線程優點:能提高程式的運作效率,CPU、記憶體使用率。
多線程缺點:
開銷:核心資料結構、棧空間(子線程512KB,主線程1MB),也可以使用setStackSize設定,但必須是4k的倍數,最小16k,建立線程大約需要90ms的時間。
複雜:如果開啟太多線程會降低程式的性能,同時程式的設計也更加複雜(線程間的通信、多線程間的資料共享)。
- 主線程
一個iOS程式運作後,預設會開啟一條線程,稱為主線程或UI線程。主要作用有:
- 顯示重新整理UI界面
處理UI事件(點選、滾動、拖拽)。
是以将耗時操作放到主線程,會導緻卡頓等。
//獲得主線程
NSThread *thread = [NSThread mainThread];
//獲得目前線程
NSThread *curThread = [NSThread currentThread]
- 線程的幾種狀态
二、多線程的實作方案
技術方案 | 簡介 | 語言 | 線程生命後期 | 使用頻 |
pthread |
| C | 程式員管理 | 幾乎不用 |
NSThread |
| OC | 程式員管理 | 偶爾使用 |
|
| C | 自動管理 | 經常使用 |
NSOperation |
| OC | 自動管理 | 經常使用 |
1、模拟線程阻塞
Main.storyboard上拖入一個按鈕,在拖入一個UITextView,按鈕點選方法裡循環列印,運作後點選按鈕,此時滑動UITextView發現不能滑動。原因就是目前主線程循環列印沒有執行完,此時UITextView的滑動事件還不能處理,即線程阻塞,通過建立一個新線程處理。
//阻塞主線程
- (IBAction)btn:(UIButton *)sender {
for (int i = 0; i < 100000; i++) {
NSLog(@"%d", i);
}
}
1、 pthread
1.1 pthread的使用
/**
pthread的使用
*/
- (void)pthread {
//引入頭檔案#import <pthread.h>
//1. 建立線程: 定義一個pthread_t類型變量
pthread_t thread = nil;
/**
@para 線程對象
@pare 線程的屬性(優先級)
@para 指向函數的指針
@para 傳給第三個參數的參數
*/
// 2. 開啟線程: 執行任務
pthread_create(&thread, NULL, run, NULL);
//設定子線程的狀态設定為 detached,該線程運作結束後會自動釋放所有資源
pthread_detach(thread);
}
//技巧:把<#void * _Nullable (* _Nonnull)(void * _Nullable)#>直接粘過來,(*)改寫成函數的名稱,補全參數
void *run(void *str) {//新線程調用方法,裡邊為需要執行的任務
NSLog(@"%@", [NSThread currentThread]);
return NULL;
}
由于pthread在iOS中幾乎不用,是以不做過多介紹
2、NSThread
NSThread是輕量級的多線程開發,使用起來也并不複雜,但是使用NSThread需要自己管理線程生命周期。
2.1 NSThread的基本使用
/**
類方法的 Block和SEL方式,建立後就處于就緒狀态,拿不到線程對象适合就用一次的線程
*/
+ (void)detachNewThreadWithBlock:(void (^)(void))block;
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
/**
屬性方法裡的SEL方式可以拿到線程對象,要調用start方法啟動,把建立的線程添加到可排程線程池,即讓線程處于就緒狀态,當系統排程時才真正執行
*/
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument;
- (instancetype)initWithBlock:(void (^)(void))block;
/**
隐式的在背景建立一個線程
*/
[self performSelectorInBackground:@selector(run) withObject:nil];
2.2 NSThread其它方法
- 線程的狀态
/**
線程狀态分為isExecuting(正在執行)、isFinished(已經完成)、isCancellled(已經取消)三種。其中取消狀态程式可以幹預設定,隻要調用線程的cancel方法即可。但是需要注意在主線程中僅僅能設定線程狀态,并不能真正停止目前線程,如果要終止線程必須線上程中調用exist方法,這是一個靜态方法,調用該方法可以退出目前線程。
*/
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled ;
- 線程優先級
/** NSQualityOfService:
NSQualityOfServiceUserInteractive:最高優先級,主要用于提供互動UI的操作,比如處理點選事件,繪制圖像到螢幕上
NSQualityOfServiceUserInitiated:次高優先級,主要用于執行需要立即傳回的任務
NSQualityOfServiceDefault:預設優先級,當沒有設定優先級的時候,線程預設優先級
NSQualityOfServiceUtility:普通優先級,主要用于不需要立即傳回的任務
NSQualityOfServiceBackground:背景優先級,用于完全不緊急的任務
*/
@property NSQualityOfService qualityOfService;
//注意read-only after the thread is started
/**
還可以同具體的值設定優先級,排程優先級的取值範圍是0.0 ~ 1.0,預設0.5,值越大,優先級越高,優先級高的被CPU調用的機率更高。
*/
+(double)threadPriority;
- 線程的阻塞
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
/**
線程中函數的調用位址和名字,通常用來和NSLog聯調
*/
@property (class, readonly, copy) NSArray<NSNumber *> *callStackReturnAddresses;
@property (class, readonly, copy) NSArray<NSString *> *callStackSymbols;
2.3 線程間通信(NSThread對NSObject分類擴充)
在開發中,我們經常會在子線程進行耗時操作比如網絡請求、圖檔下載下傳等,那麼子線程如何告訴主線程圖檔下載下傳已經完成,主線程中好拿到圖檔更新UI,這就涉及到了線程間的通信。
/**
指定方法在主線程中執行
參數1. SEL 方法
2.方法參數
3.是否等待目前執行完畢
4.指定的Runloop model
*/
- (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;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
// equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;
2.4線程的生命周期
重新建立一個類,繼承與NSThread,重寫dealloc方法,線程的run方法裡是for循環100次,可以看到線程2在循環結束即線程内的任務完成之後就釋放。
不同線程請求同一塊記憶體資源就涉及到線程安全的問題,加鎖!至于加什麼鎖、如何加鎖、以及鎖的性能單獨總結一篇文章,這裡先不說。