線程
一個線程中任務(wù)的執(zhí)行是串行的
同一時間內(nèi),一個線程只能執(zhí)行一個任務(wù)
線程是進程中的一條執(zhí)行路徑
多線程
一個進程中可以開啟多條線程愚墓,多條線程可以并行(同時)執(zhí)行不同的任務(wù)
多線程原理
同一時間懂讯,CPU只能處理1條線程,只有一條線程在工作(執(zhí)行)
多線程并發(fā)(同時)執(zhí)行,其實是CPU快速在多條線程之間調(diào)度(切換)
多核CPU是真正意義上的多線程
CPU在多個線程之間調(diào)度屿衅,會消耗大量的CPU資源
每條線程被調(diào)度執(zhí)行的頻次會降低(線程執(zhí)行效率會降低)
優(yōu)點:
能適當(dāng)提高程序的執(zhí)行效率
缺點:
創(chuàng)建線程是有開銷的畅卓,iOS下主要成本包括:內(nèi)核數(shù)據(jù)結(jié)構(gòu)(大約1KB)擅腰、棧空間(子線程512KB翁潘、主線程1MB)
也可以使用-setStackSize設(shè)置趁冈,但必須是4K的倍數(shù),而且最小為16K拜马,創(chuàng)建線程大約需要90毫秒的創(chuàng)建時間
開啟大量的線程渗勘,會降低程序的性能
線程越多,CPU在調(diào)度線程上的開銷越大
程序設(shè)計更加復(fù)雜:比如線程之間的通信俩莽、多線程的數(shù)據(jù)共享
多線程在iOS中的應(yīng)用
一個iOS程序運行后旺坠,默認開啟一個線程,稱為主線程或UI線程
主線程的作用:
顯示豹绪、刷新UI界面
處理UI事件(點擊价淌、滾動、拖拽事件等)
多線程使用注意:
別將比較耗時的操作放到主線程中瞒津,耗時操作會卡住主線程蝉衣,嚴(yán)重影響UI流暢度
iOS多線程實現(xiàn)方案
- pthread C 通用的多線程API,跨平臺巷蚪,程序員手動管理線程生命周期病毡,使用難度大,iOS中幾乎不用
- NSThread OC 面向?qū)ο笃ò兀唵我子美材ぃ回撠?zé)創(chuàng)建有送,不用管理線程死亡,偶爾使用
- GCD C 替代NSThread等線程技術(shù)僧家,可以充分利用設(shè)備的多核雀摘,線程生命周期自動管理,經(jīng)常使用
- NSOperation OC 基于GCD(底層是GCD)八拱,比GCD多了一些簡單實用的功能阵赠,更加面向?qū)ο螅詣庸芾砭€程生命周期肌稻,經(jīng)常使用清蚀。
pthread
需要引入#import <pthread.h>
//創(chuàng)建方法
int pthread_create(pthread_t * __restrict, const pthread_attr_t * __restrict,
void *(*)(void *), void * __restrict);
//使用
pthread_t thread;
pthread_create(&thread, NULL, func, NULL);
void * func(void *param)
{
for (NSInteger i = 0; i<50000; i++) {
NSLog(@"----%zd--%@", i, [NSThread currentThread]);
}
return NULL;
}
NSThread
創(chuàng)建、啟動線程
- 方法一
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(func) object:nil];
[thread start];
//線程啟動時爹谭,就會在線程thread中執(zhí)行self中的func方法
- 方法二
//創(chuàng)建線程后自動啟動線程
[NSThread detachNewThreadSelector:(SEL) toTarget:(id) withObject:(id)];
- 方法三
//隱式創(chuàng)建并啟動線程
[self performSelectorInBackground:(SEL) withObject:(id)];
主線程相關(guān)用法:
+ (NSThread *)mainThread; //獲得主線程
+ (BOOL)isMainThread; //是否為主線程
- (BOOL)isMainThread; //是否為主線程
線程阻塞:
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
線程死亡:
- (void)exit; // 強制退出線程
GCD
會自動利用更多的CPU內(nèi)核枷邪,會自動管理線程的生命周期,不需要寫管理線程的代碼诺凡。定制任務(wù) 將任務(wù)添加到隊列中 GCD會自動將隊列中的任務(wù)取出东揣,放到線程中去執(zhí)行,任務(wù)的取出遵循FIFO原則绑洛。
任務(wù) (是否有開啟新線程的能力)
- 同步
在當(dāng)前線程中執(zhí)行
dispatch_sync(dispatch_queue_t queue, ^(void)block)
- 異步
可以在新的線程中執(zhí)行,有開新線程的能力(不是一定會開新線程救斑,比如放在主隊列中)
dispatch_async(dispatch_queue_t queue, ^(void)block)
隊列 (影響任務(wù)的執(zhí)行方式)
- 并發(fā) (允許開啟多個線程)
// 創(chuàng)建并發(fā)隊列
dispatch_queue_t queue = dispatch_queue_create("queue.com", DISPATCH_QUEUE_CONCURRENT);
- 串行
// 創(chuàng)建串行隊列
dispatch_queue_t queue = dispatch_queue_create("queue.com", DISPATCH_QUEUE_SERIAL);
// DISPATCH_QUEUE_SERIAL 可以傳NULL
獲取全局并發(fā)隊列
// 兩個參數(shù) 優(yōu)先級 保留參數(shù)傳0
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
獲取全局串行主隊列(主隊列中的任務(wù)都在主線程中執(zhí)行)
dispatch_queue_t queue = dispatch_get_main_queue();
不可以在主隊列中添加同步任務(wù)童本,因為同步任務(wù)立刻會執(zhí)行真屯,選成死鎖
| | 并發(fā)隊列 | 手動創(chuàng)建的串行隊列 | 主隊列|
| ------------- |:-------------:| -----:|
| 同步sync |沒有開啟新線程 串行執(zhí)行任務(wù)|沒有開啟新線程 串行執(zhí)行任務(wù)|沒有開啟新線程 串行執(zhí)行任務(wù)|
| 異步async |有開啟新線程 并發(fā)執(zhí)行任務(wù)|有開啟新線程 串行執(zhí)行任務(wù)|沒有開啟新線程 串行執(zhí)行任務(wù)|
GCD一些方法示例
/** 并發(fā) 異步 全局并發(fā)隊列 */
- (void)GCDTest1 {
/**
@param identifier 優(yōu)先級
@param flags 保留參數(shù) 默認傳0
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"-------1-------");
});
dispatch_async(queue, ^{
NSLog(@"-------2-------");
});
dispatch_async(queue, ^{
NSLog(@"-------3-------");
});
}
/** 并發(fā) 異步 自定義并發(fā)隊列 */
- (void)GCDTest2 {
dispatch_queue_t queue = dispatch_queue_create("queue.com", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"-------1-------");
});
dispatch_async(queue, ^{
NSLog(@"-------2-------");
});
dispatch_async(queue, ^{
NSLog(@"-------3-------");
});
}
/** 并發(fā) 異步 自定義并發(fā)隊列 圍欄函數(shù) */
- (void)GCDTest3 {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"-------1-------");
});
dispatch_async(queue, ^{
NSLog(@"-------2-------");
});
dispatch_barrier_async(queue, ^{
NSLog(@"-------圍欄函數(shù)-------");
});
dispatch_async(queue, ^{
NSLog(@"-------3-------");
});
}
/** 利用一次性函數(shù)寫單列 */
+ (instancetype)GCDTest4 {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_singleton = [[Singleton alloc] init];
});
return _singleton;
}
/** 延遲執(zhí)行 */
- (void)GCDTest5 {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"延時2秒打印");
});
}
/** GCD group */
- (void)GCDTest6 {
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"-------1-------");
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"-------2-------");
});
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"-------3-------");
});
}
多線程的安全隱患
資源共享:
一塊資源可能會被多個線程共享,比如多個線程訪問同一個對象穷娱、對一個變量绑蔫、同一個文件
解決方法:
互斥鎖
@synchronized(鎖對象){ //需要鎖定的代碼 }
不推薦使用,消耗性能
鎖定1份代碼只能用1把鎖泵额,用多把是無效的
線程同步技術(shù)
原子和非原子屬性
atomic 默認 原子屬性 為setter方法加鎖
線程安全配深,需要消耗大量的資源
nonatomic 非原子屬性 不會為setter方法加鎖
非線程安全,適合內(nèi)存小的移動設(shè)備
建議:
所有屬性都聲明為nonatomic
盡量避免多線程搶奪同一塊資源
盡量加加鎖嫁盲、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器處理篓叶,減小移動客戶端壓力
線程之間的通信
在一個進程中,線程往往不是孤立存在的羞秤,多個線程之間需要經(jīng)常進行通信
如:
一個線程傳遞數(shù)據(jù)給另一個線程
在一個線程中執(zhí)行完特定任務(wù)后缸托,轉(zhuǎn)到另一個線程中繼續(xù)執(zhí)行任務(wù)
常用方法:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
NSPort 端口對象 在兩個線程之間通信
NSMessagePort
NSMachPort