一、多線程基礎(chǔ)
基本概念
進(jìn)程
進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序
每個(gè)進(jìn)程之間是獨(dú)立的篙顺,每個(gè)進(jìn)程均運(yùn)行在其專(zhuān)用且受保護(hù)的內(nèi)存空間內(nèi)
通過(guò) 活動(dòng)監(jiān)視器 可以查看 Mac 系統(tǒng)中所開(kāi)啟的進(jìn)程線程
進(jìn)程要想執(zhí)行任務(wù)面褐,必須得有線程拌禾,進(jìn)程至少要有一條線程
程序啟動(dòng)會(huì)默認(rèn)開(kāi)啟一條線程,這條線程被稱為主線程或UI 線程
線程是進(jìn)程的基本執(zhí)行單元展哭,進(jìn)程的所有任務(wù)都在線程中執(zhí)行-
多線程
- 一個(gè)進(jìn)程中可以開(kāi)啟多條線程湃窍,每條線程可以同時(shí)執(zhí)行不同的任務(wù)
進(jìn)程 -> 公司
線程 -> 員工
主線程 -> 老板(第一個(gè)員工) - 多線程技術(shù)可以提高程序的執(zhí)行效率
- 一個(gè)進(jìn)程中可以開(kāi)啟多條線程湃窍,每條線程可以同時(shí)執(zhí)行不同的任務(wù)
- 多線程原理
- 同一時(shí)間,CPU只能處理一條線程匪傍,只有一條線程在執(zhí)行
- 多線程同時(shí)執(zhí)行您市,其實(shí)是CPU快速地在多條線程之間切換
- 如果CPU調(diào)度線程的時(shí)間足夠快,就造成了多線程并發(fā)執(zhí)行的假象
- 如果線程非常多析恢,會(huì)在多條線程之間來(lái)回切換墨坚,消耗大量的 CPU 資源
- 每個(gè)線程被調(diào)度的次數(shù)會(huì)降低
- 線程的執(zhí)行效率會(huì)下降
iOS 8.0 主線程的默認(rèn)堆棧大小也是 512K
-
多線程優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)
能適當(dāng)提高程序的執(zhí)行效率缺點(diǎn)
開(kāi)啟線程需要占用一定的內(nèi)存空間映挂,如果開(kāi)啟大量的線程泽篮,會(huì)占用大量的內(nèi)存空間,降低程序的性能
線程越多柑船,CPU在調(diào)度線程上的開(kāi)銷(xiāo)就越大
程序設(shè)計(jì)更加復(fù)雜:比如線程之間的通信帽撑、多線程的數(shù)據(jù)共享
-
主線程
- 程序啟動(dòng)創(chuàng)建的線程,被稱為主線程或UI線程
- 主線程的作用
- 顯示/刷新 UI 界面
- 處理 UI 事件:點(diǎn)擊鞍时、滾動(dòng)亏拉、拖拽等事件
- 注意:要將耗時(shí)操作放在后臺(tái)線程執(zhí)行,否則會(huì)影響 UI 的流暢度逆巍,破壞用戶體驗(yàn)
- 所有網(wǎng)絡(luò)訪問(wèn)都是耗時(shí)操作及塘!
- iOS中多線程的實(shí)現(xiàn)方案
二、耗時(shí)操作示例
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 在主線程執(zhí)行
[self longOperation];
// 在后臺(tái)線程執(zhí)行
//[self performSelectorInBackground:@selector(longOperation) withObject:nil];
}
// 耗時(shí)操作
- (void)longOperation{
NSLog(@"start = %@",[NSThread currentThread]);
int largeNumber = 1000 * 1000 * 10;
for (int index = 0; index < largeNumber; index ++) {
// 棧區(qū)
// int num = 10;
// 靜態(tài)區(qū)/常量區(qū)
// NSString *str = @"hello world";
// 在 oc 中,只要使用 @"" 定義的字符串,如果內(nèi)容一樣,無(wú)論在哪里,地址都一樣锐极。
// stringWithFormat:生成的字符串是保存在堆區(qū)的
// 棧區(qū)操作效率要比堆區(qū)快
// 程序員只需要管理堆區(qū)的內(nèi)存
NSString *str = [NSString stringWithFormat:@"hello world - %d",index];
}
NSLog(@"over");
}
[NSThread currentThread] 是獲取當(dāng)前線程的對(duì)象笙僚。
最常用的就是根據(jù) number 判斷是否主線程。
number == 1 就是主線程 灵再。
number != 1 就是后臺(tái)線程肋层。
不要糾結(jié) number 的具體數(shù)字亿笤,由 CPU 決定。
演示因耗時(shí)操作導(dǎo)致按鈕和 UITextView 不能繼續(xù)響應(yīng)用戶點(diǎn)擊和拖拽事件栋猖。
學(xué)習(xí)多線程的目的:就是將耗時(shí)操作放到后臺(tái)去執(zhí)行净薛。
三、pthread
-
1蒲拉、簡(jiǎn)介
- pthread 是 POSIX 多線程開(kāi)發(fā)框架肃拜,由于是跨平臺(tái)的 C 語(yǔ)言框架,在蘋(píng)果的頭文件中并沒(méi)有詳細(xì)的注釋雌团。
- 要查閱 pthread 有關(guān)資料爆班,可以訪問(wèn) http://baike.baidu.com。
2辱姨、導(dǎo)入頭文件
#import <pthread.h>
- 3、pthread示例
// 創(chuàng)建線程戚嗅,并且在線程中執(zhí)行 demo 函數(shù)
- (void)pthreadDemo {
/**
參數(shù):
1> 指向線程標(biāo)識(shí)符的指針雨涛,C 語(yǔ)言中類(lèi)型的結(jié)尾通常 _t/Ref,而且不需要使用 *
-- 在 C 語(yǔ)言中懦胞,沒(méi)有對(duì)象的概念替久,對(duì)象是以結(jié)構(gòu)體的方式來(lái)實(shí)現(xiàn)的。
---- 通常躏尉,在 C 語(yǔ)言框架中蚯根,對(duì)象類(lèi)型以 _t/Ref 結(jié)尾,而且聲明時(shí)不需要使用 *
2> 用來(lái)設(shè)置線程屬性
3> 線程運(yùn)行函數(shù)的起始地址
--- 在 C 語(yǔ)言中胀糜,函數(shù)名就是指向函數(shù)在內(nèi)存中的起始地址
--- 類(lèi)似的一個(gè)概念:數(shù)組名是指向數(shù)組第一個(gè)元素的地址颅拦。
在 C 語(yǔ)言中, void *(指向任何地址的指針) 和 OC 中的 id(萬(wàn)能指針) 是等價(jià)的
參數(shù)3的格式: void * (*) (void *)
返回值 (*函數(shù)指針) (參數(shù))
4> 運(yùn)行函數(shù)的參數(shù)
返回值:
- 若線程創(chuàng)建成功教藻,則返回0
- 若線程創(chuàng)建失敗距帅,則返回出錯(cuò)編號(hào)
*/
pthread_t threadId = NULL;
NSString *str = @"Hello Pthread";
int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(str));
if (result == 0) {
NSLog(@"創(chuàng)建線程 OK");
} else {
NSLog(@"創(chuàng)建線程失敗 %d", result);
}
}
// 后臺(tái)線程調(diào)用函數(shù)
void *demo(void *params) {
NSString *str = (__bridge NSString *)(params);
NSLog(@"%@ - %@", [NSThread currentThread], str);
return NULL;
}
- 4、小結(jié)
- 在 C 語(yǔ)言中括堤,沒(méi)有對(duì)象的概念碌秸,對(duì)象是以結(jié)構(gòu)體的方式來(lái)實(shí)現(xiàn)的。
- 通常悄窃,在 C 語(yǔ)言框架中讥电,對(duì)象類(lèi)型以 _t/Ref 結(jié)尾,而且聲明時(shí)不需要使用 *
- C 語(yǔ)言中的 void * 和 OC 中的 id 是等價(jià)的
- 內(nèi)存管理
- 在 OC 中轧抗,如果是 ARC 開(kāi)發(fā)恩敌,編譯器會(huì)在編譯時(shí),會(huì)根據(jù)代碼結(jié)構(gòu)鸦致,自動(dòng)添加retain/release/autorelease
- 但是潮剪,ARC 只負(fù)責(zé)管理 OC 部分的內(nèi)存管理涣楷,而不負(fù)責(zé) C 語(yǔ)言 代碼的內(nèi)存管理
- 因此,開(kāi)發(fā)過(guò)程中抗碰,如果使用的 C 語(yǔ)言框架出現(xiàn) retain/create/copy/new 等字樣的函數(shù)狮斗,大多都需要 release,否則會(huì)出現(xiàn)內(nèi)存泄漏
- 在混合開(kāi)發(fā)時(shí)弧蝇,如果在 C 和 OC 之間傳遞數(shù)據(jù)碳褒,需要使用 __bridge 進(jìn)行橋接,橋接的目的就是為了告訴編譯器如何管理內(nèi)存看疗。__bridge 表示什么特殊處理都不做沙峻。
- 橋接的添加可以借助 Xcode 的輔助功能添加。
- MRC 中不需要使用橋接两芳。因?yàn)镸RC的內(nèi)存管理需要程序員手動(dòng)管理摔寨。
四、NSThread
4.1怖辆、創(chuàng)建線程的方式(3種)
準(zhǔn)備在后臺(tái)線程調(diào)用的方法 longOperation:
- (void)longOperation:(id)obj {
NSLog(@"%@ - %@", [NSThread currentThread], obj);
}
4.1.1是复、alloc / init - start
- (void)threadDemo1 {
// 創(chuàng)建線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download) object:@"Alloc"];
// 開(kāi)啟線程
[thread start];
NSLog(@"after %@", [NSThread currentThread]);
}
- (void)download {
}
- 小結(jié)
1.[thread start];執(zhí)行后竖螃,會(huì)在另外一個(gè)線程執(zhí)行 longOperation: 方法
2.在 OC 中淑廊,任何一個(gè)方法的代碼都是從上向下順序執(zhí)行的
3.同一個(gè)方法內(nèi)的代碼,都是在相同線程執(zhí)行的(block除外)
4.1.2特咆、detachNewThreadSelector
- (void)threadDemo2 {
NSLog(@"before %@", [NSThread currentThread]);
[NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self withObject:@"DETACH"];
NSLog(@"after %@", [NSThreadcurrentThread]);
}
或:
- (void)threadDemo2 {
// 在同一個(gè)方法中,代碼是從上往下執(zhí)行的.
// 同一個(gè)線程中,代碼也是從上往下執(zhí)行的(block除外)
// 多線程開(kāi)發(fā),不要相信第一次執(zhí)行的結(jié)果
NSLog(@"start = %@",[NSThread currentThread]);
// detach:分離
// 創(chuàng)建線程,并啟動(dòng)線程.
// [self download:@"xxx"];
// 創(chuàng)建線程本身是在主線程創(chuàng)建,
[NSThread detachNewThreadSelector:@selector(download:) toTarget:self.person withObject:@"detach"];
NSLog(@"over");
}
- 小結(jié)
detachNewThreadSelector 類(lèi)方法不需要啟動(dòng)季惩,會(huì)自動(dòng)創(chuàng)建線程并執(zhí)行 @selector 方法。
4.1.3腻格、分類(lèi)方法:performSelectorInBackground
- (void)threadDemo3 {
NSLog(@"before %@", [NSThread currentThread]);
[self performSelectorInBackground:@selector(longOperation) withObject:@"PERFORM"];
NSLog(@"after %@", [NSThread currentThread]);
}
- (void)longOperation {
}
- 小結(jié)
1.performSelectorInBackground 是 NSObject 的分類(lèi)方法画拾。
2.會(huì)自動(dòng)在后臺(tái)線程執(zhí)行 @selector 方法。
3.沒(méi)有 thread 字眼菜职,隱式創(chuàng)建并啟動(dòng)線程碾阁。
4.所有 NSObject 都可以使用此方法,在其他線程執(zhí)行方法
4.1.4些楣、創(chuàng)建和啟動(dòng)線程
一個(gè)NSThread對(duì)象就代表一條線程
創(chuàng)建脂凶、啟動(dòng)線程
NSThread*thread = [[NSThreadalloc] initWithTarget:selfselector:@selector(run) object:nil];
[thread start];
// 線程一啟動(dòng),就會(huì)在線程thread中執(zhí)行self的run方法
- 主線程相關(guān)用法
+ (NSThread*)mainThread;// 獲得主線程
- (BOOL)isMainThread;// 是否為主線程
+ (BOOL)isMainThread;// 是否為主線程
4.1.5愁茁、其他用法
獲得當(dāng)前線程
NSThread *current = [NSThreadcurrentThread];
線程的調(diào)度優(yōu)先級(jí)
+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;
- (double)threadPriority;
- (BOOL)setThreadPriority:(double)p;
調(diào)度優(yōu)先級(jí)的取值范圍是0.0~1.0蚕钦,默認(rèn)0.5,值越大鹅很,優(yōu)先級(jí)越高
線程的名字
- (void)setName:(NSString*)name;
- (NSString*)name;
4.1.6嘶居、其他創(chuàng)建線程方式
創(chuàng)建線程后自動(dòng)啟動(dòng)線程
[NSThreaddetachNewThreadSelector:@selector(run) toTarget:selfwithObject:nil];
隱式創(chuàng)建并啟動(dòng)線程
[selfperformSelectorInBackground:@selector(run) withObject:nil];
- 上述2種創(chuàng)建線程方式的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):簡(jiǎn)單快捷
缺點(diǎn):無(wú)法對(duì)線程進(jìn)行更詳細(xì)的設(shè)置
4.2、NSThread的Target
NSThread 的實(shí)例化方法中的 target 指的是開(kāi)啟線程后,在線程中執(zhí)行 哪一個(gè)對(duì)象 的 @selector 方法邮屁。
4.3整袁、示例
準(zhǔn)備對(duì)象
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
+ (instancetype)personWithDict:(NSDictionary *)dict {
id obj = [[self alloc] init];
[obj setValuesForKeysWithDictionary:dict];
return obj;
}
- (void)longOperation:(id)obj {
NSLog(@"%@ - %@ - %@", [NSThreadcurrentThread], self.name, obj);
}
@end
定義屬性 :
@property (nonatomic, strong) Person *person;
懶加載
- (Person *)person {
if (_person == nil) {
_person = [Person personWithDict:@{@"name": @"zhangsan"}];
}
return _person;
}
三種線程調(diào)度方法
1、alloc / init
NSThread *thread = [[NSThread alloc] initWithTarget:self.person(調(diào)用者) selector:@selector(longOperation:)(調(diào)用者調(diào)用此方法) object(參數(shù)): @"THREAD"];
[thread start];
2佑吝、Detach (分離)
[NSThread detachNewThreadSelector:@selector(longOperation:) toTarget:self.person withObject:@"DETACH"];
3坐昙、分類(lèi)方法(創(chuàng)建一個(gè)后臺(tái)子線程并運(yùn)行)
[self.person performSelectorInBackground:@selector(longOperation:) withObject:@"PERFORM"];
- 小結(jié)
通過(guò)指定不同的 target 會(huì)在后臺(tái)線程執(zhí)行該對(duì)象的 @selector 方法
提示:不要看見(jiàn) target 就寫(xiě) self
performSelectorInBackground 可以讓方便地在后臺(tái)線程執(zhí)行任意 NSObject 對(duì)象的方法
4.4、線程狀態(tài)
新建
實(shí)例化線程對(duì)象就緒
向線程對(duì)象發(fā)送 start 消息芋忿,線程對(duì)象被加入 可調(diào)度線程池 等待 CPU 調(diào)度
detach 方法和 performSelectorInBackground 方法會(huì)直接實(shí)例化一個(gè)線程對(duì)象并加入 可調(diào)度線程池運(yùn)行
CPU 負(fù)責(zé)調(diào)度可調(diào)度線程池中線程的執(zhí)行
線程執(zhí)行完成之前炸客,狀態(tài)可能會(huì)在就緒和運(yùn)行之間來(lái)回切換
就緒和運(yùn)行之間的狀態(tài)變化由 CPU 負(fù)責(zé),程序員不能干預(yù)阻塞
當(dāng)滿足某個(gè)預(yù)定條件時(shí)戈钢,可以使用休眠或鎖阻塞線程執(zhí)行:
sleepForTimeInterval:休眠指定時(shí)長(zhǎng)
sleepUntilDate:休眠到指定日期
@synchronized(self):互斥鎖
-
死亡
正常死亡
線程執(zhí)行完畢非正常死亡
當(dāng)滿足某個(gè)條件后痹仙,在線程內(nèi)部中止執(zhí)行。
當(dāng)滿足某個(gè)條件后殉了,在主線程中止線程對(duì)象开仰。
[NSThread exit];
一旦強(qiáng)行終止線程,后續(xù)的所有代碼都不會(huì)被執(zhí)行
注意:在終止線程之前薪铜,應(yīng)該注意釋放之前分配的對(duì)象抖所!
控制線程狀態(tài)
啟動(dòng)線程
- (void)start;
// 進(jìn)入就緒狀態(tài) ->運(yùn)行狀態(tài)。當(dāng)線程任務(wù)執(zhí)行完畢痕囱,自動(dòng)進(jìn)入死亡狀態(tài)
阻塞(暫停)線程
+ (void)sleepUntilDate:(NSDate*)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 進(jìn)入阻塞狀態(tài)
強(qiáng)制停止線程
+ (void)exit;
// 進(jìn)入死亡狀態(tài)
注意:一旦線程停止(死亡)了,就不能再次開(kāi)啟任務(wù)
4.4.1暴匠、示例代碼
- (void)statusDemo {
NSLog(@"先睡會(huì)");
[NSThread sleepForTimeInterval:1.0];
for (int i = 0; i < 20; i++) {
if (i == 9) {
NSLog(@"再睡會(huì)");
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
}
NSLog(@"%d %@", i, [NSThreadcurrentThread]);
if (i == 16) {
NSLog(@"88");
// 終止線程之前鞍恢,需要記住釋放資源
[NSThread exit];
}
}
NSLog(@"over");
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 注意不要在主線程上調(diào)用 exit 方法
// [NSThread exit];
// 實(shí)例化線程對(duì)象(新建)
NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(statusDemo) object:nil];
// 線程就緒(被添加到可調(diào)度線程池中)
[t start];
}
4.4.2、取消線程
- (void)download{
NSThread *thread = [NSThread currentThread];
// 判斷線程是否取消
if (thread.isCancelled) {
NSLog(@"1...888");
return;
}
// 睡0.2秒
[NSThread sleepForTimeInterval:0.2];
NSLog(@"睡會(huì)");
for (int index = 0; index < 10; index ++) {
if (thread.isCancelled) {
NSLog(@"2...888");
return;
}
NSLog(@"%@",[NSThread currentThread]);
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 創(chuàng)建
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download) object:nil];
// 就緒 -> 進(jìn)入 CPU 的可調(diào)用線程池
[thread start];
// 休眠一會(huì)
[NSThread sleepForTimeInterval:0.2];
// 取消線程 cancel 是給線程發(fā)送一個(gè)取消的消息每窖。設(shè)置線程的狀態(tài)為取消帮掉。
// 但是:如果要線程終止,需要在線程內(nèi)部判斷窒典。
[thread cancel];
}
4.4.3蟆炊、小結(jié)
- 阻塞
方法執(zhí)行過(guò)程,符合某一條件時(shí)瀑志,可以利用 sleep 方法讓線程進(jìn)入 阻塞 狀態(tài)
sleepForTimeInterval // 從現(xiàn)在起睡多少秒
sleepUntilDate // 從現(xiàn)在起睡到指定的日期
- 死亡
[NSThread exit];
一旦強(qiáng)行終止線程涩搓,后續(xù)的所有代碼都不會(huì)被執(zhí)行
注意:在終止線程之前,應(yīng)該注意釋放之前分配的對(duì)象劈猪!
- 注意:線程從就緒和運(yùn)行狀態(tài)之間的切換是由 CPU 負(fù)責(zé)的昧甘,程序員無(wú)法干預(yù)
4.5、線程的屬性
name - 線程名稱(需要設(shè)置)
在大的商業(yè)項(xiàng)目中战得,通常需要在程序崩潰時(shí)充边,獲取程序準(zhǔn)確執(zhí)行所在的線程。threadPriority - 線程優(yōu)先級(jí)
優(yōu)先級(jí)常侦,是一個(gè)浮點(diǎn)數(shù)浇冰,取值范圍從 0~1.0
1.0表示優(yōu)先級(jí)最高
0.0表示優(yōu)先級(jí)最低
默認(rèn)優(yōu)先級(jí)是0.5
優(yōu)先級(jí)高只是保證 CPU 調(diào)度頻率的可能性會(huì)高
建議:在開(kāi)發(fā)的時(shí)候贬媒,不要修改優(yōu)先級(jí),調(diào)度頻率快慢由 CPU決定肘习。
多線程的目的:是將耗時(shí)的操作放在后臺(tái)际乘,不阻塞主線程和用戶的交互礁竞!
多線程開(kāi)發(fā)的原則:簡(jiǎn)單
-
stackSize - 棧區(qū)大小
默認(rèn)情況下突梦,無(wú)論是主線程還是子線程览效,棧區(qū)大小都是 512K
棧區(qū)大小可以設(shè)置[NSThread currentThread].stackSize = 1024 * 1024;
- isMainThread - 是否主線程
4.5.1敬尺、示例代碼
// MARK: - 線程屬性
- (void)threadProperty {
NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
// 1. 線程名稱
t1.name = @"Thread AAA";
// 2. 優(yōu)先級(jí)
t1.threadPriority = 0;
[t1 start];
NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
// 1. 線程名稱
t2.name = @"Thread BBB";
// 2. 優(yōu)先級(jí)
t2.threadPriority = 1;
[t2 start];
}
- (void)demo {
for (int i = 0; i < 10; ++i) {
// 堆棧大小
NSLog(@"%@ 堆棧大斜锨础:%tuK", [NSThreadcurrentThread], [NSThread currentThread].stackSize / 1024);
}
// 判斷是否是主線程
if (![NSThread currentThread].isMainThread) {
}
}
4.6太颤、資源共享
4.6.1陈瘦、多線程的安全隱患
資源共享
1塊資源可能會(huì)被多個(gè)線程共享启搂,也就是多個(gè)線程可能會(huì)訪問(wèn)同一塊資源
比如多個(gè)線程訪問(wèn)同一個(gè)對(duì)象墓拜、同一個(gè)變量港柜、同一個(gè)文件
當(dāng)多個(gè)線程訪問(wèn)同一塊資源時(shí),很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問(wèn)題
安全隱患解決 – 互斥鎖
互斥鎖使用格式
@synchronized(鎖對(duì)象) { // 需要鎖定的代碼 }注意:鎖定1份代碼只用1把鎖咳榜,用多把鎖是無(wú)效的
互斥鎖的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問(wèn)題
缺點(diǎn):需要消耗大量的CPU資源互斥鎖的使用前提:多條線程搶奪同一塊資源
相關(guān)專(zhuān)業(yè)術(shù)語(yǔ):線程同步
線程同步的意思是:多條線程在同一條線上執(zhí)行(按順序地執(zhí)行任務(wù))
互斥鎖夏醉,就是使用了線程同步技術(shù)
4.6.2、資源共享-賣(mài)票
- 多線程開(kāi)發(fā)的復(fù)雜度相對(duì)較高涌韩,在開(kāi)發(fā)時(shí)可以按照以下套路編寫(xiě)代碼:
1.首先確保單個(gè)線程執(zhí)行正確
2.添加線程
- 賣(mài)票邏輯
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.tickets = 20;
[self saleTickets];
}
/// 賣(mài)票邏輯 - 每一個(gè)售票邏輯(窗口)應(yīng)該把所有的票賣(mài)完
- (void)saleTickets {
while (YES) {
if (self.tickets > 0) {
self.tickets--;
NSLog(@"剩余票數(shù) %d %@", self.tickets, [NSThread currentThread]);
} else {
NSLog(@"沒(méi)票了 %@", [NSThreadcurrentThread]);
break;
}
}
}
添加線程
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.tickets = 20;
NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
t1.name = @"售票員 A";
[t1 start];
NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
t2.name = @"售票員 B";
[t2 start];
}
添加休眠
- (void)saleTickets {
while (YES) {
// 模擬休眠
[NSThreadsleepForTimeInterval:1.0];
if (self.tickets > 0) {
self.tickets--;
NSLog(@"剩余票數(shù) %d %@", self.tickets, [NSThread currentThread]);
} else {
NSLog(@"沒(méi)票了 %@", [NSThreadcurrentThread]);
break;
}
}
}
運(yùn)行測(cè)試結(jié)果
4.6.3畔柔、互斥鎖
- (void)saleTickets {
while (YES) {
[NSThread sleepForTimeInterval:1.0];
@synchronized(self) {
if (self.tickets > 0) {
self.tickets--;
NSLog(@"剩余票數(shù) %d %@", self.tickets, [NSThread currentThread]);
continue;
}
}
NSLog(@"沒(méi)票了 %@", [NSThreadcurrentThread]);
break;
}
}
互斥鎖小結(jié)
1.保證鎖內(nèi)的代碼,同一時(shí)間臣樱,只有一條線程能夠執(zhí)行靶擦!
2.互斥鎖的鎖定范圍,應(yīng)該盡量小雇毫,鎖定范圍越大玄捕,效率越差!
3.速記技巧 [[NSUserDefaults standardUserDefaults] synchronize];互斥鎖參數(shù)
1.能夠加鎖的任意 NSObject 對(duì)象
2.注意:鎖對(duì)象一定要保證所有的線程都能夠訪問(wèn)
3.如果代碼中只有一個(gè)地方需要加鎖棚放,大多都使用 self枚粘,這樣可以避免單獨(dú)再創(chuàng)建一個(gè)鎖對(duì)象
4.7、原子屬性
原子屬性(線程安全)飘蚯,是針對(duì)多線程設(shè)計(jì)的馍迄,是默認(rèn)屬性
多個(gè)線程在寫(xiě)入原子屬性時(shí)(調(diào)用 setter 方法),能夠保證同一時(shí)間只有一個(gè)線程執(zhí)行寫(xiě)入操作
原子屬性是一種單(線程)寫(xiě)多(線程)讀的多線程技術(shù)
原子屬性的效率比互斥鎖高局骤,不過(guò)可能會(huì)出現(xiàn)臟數(shù)據(jù)
在定義屬性時(shí)柬姚,必須顯示地指定 nonatomic,否則默認(rèn)為atomic
4.7.1庄涡、代碼演練
1量承、定義屬性
@property (nonatomic, strong) NSObject *obj1;
@property (atomic, strong) NSObject *obj2;
// 模擬原子屬性
@property (atomic, strong) NSObject *obj3;
2、模擬原子屬性
/**
如果重寫(xiě)了 atomic 屬性的 setter方法,就必須重寫(xiě) getter 方法撕捍。
- 如果同時(shí)重寫(xiě)了 setter 和 getter 方法拿穴,蘋(píng)果就不再提供_成員變量
- @synthesize 合成指令,用處就是指定屬性的 成員變量忧风。
*/
@synthesize obj3 = _obj3;
- (void)setObj3:(NSObject *)obj3 {
@synchronized(self) {
_obj3 = obj3;
}
}
- (NSObject *)obj3 {
return _obj3;
}
3默色、性能測(cè)試
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
int largeNumber = 1000 * 1000;
NSLog(@"非原子屬性");
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < largeNumber; i++) {
self.obj1 = [[NSObject alloc] init];
}
NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);
NSLog(@"原子屬性");
start = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < largeNumber; i++) {
self.obj2 = [[NSObject alloc] init];
}
NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);
NSLog(@"模擬原子屬性");
start = CFAbsoluteTimeGetCurrent();
for (int i = 0; i < largeNumber; i++) {
self.obj3 = [[NSObject alloc] init];
}
NSLog(@"%f", CFAbsoluteTimeGetCurrent() - start);
}
原子屬性內(nèi)部的鎖是自旋鎖,自旋鎖的執(zhí)行效率比互斥鎖高
atomic:原子屬性.內(nèi)部也會(huì)有一把鎖,叫做自旋鎖. 效率比互斥鎖高
4.7.2狮腿、自旋鎖&互斥鎖
1腿宰、共同點(diǎn)
都能夠保證同一時(shí)間,只有一條線程執(zhí)行鎖定范圍的代碼
2缘厢、不同點(diǎn)
互斥鎖:如果發(fā)現(xiàn)有其他線程正在執(zhí)行鎖定的代碼吃度,線程會(huì)進(jìn)入休眠狀態(tài),等待其他線程執(zhí)行完畢贴硫,打開(kāi)鎖之后椿每,線程會(huì)被喚醒
自旋鎖:如果發(fā)現(xiàn)有其他線程正在執(zhí)行鎖定的代碼,線程會(huì)以死循環(huán)的方式英遭,一直等待鎖定代碼執(zhí)行完成间护。
3、結(jié)論
自旋鎖更適合執(zhí)行非常短的代碼
無(wú)論什么鎖挖诸,都是要付出代價(jià)
4.8汁尺、線程安全
多個(gè)線程進(jìn)行讀寫(xiě)操作時(shí),仍然能夠得到正確結(jié)果多律,被稱為線程安全
要實(shí)現(xiàn)線程安全痴突,必須要用到鎖
為了得到更佳的用戶體驗(yàn),UIKit 不是線程安全的
約定:所有更新 UI 的操作都必須主線程上執(zhí)行菱涤!因此,主線程又被稱為UI 線程洛勉。
- iOS 開(kāi)發(fā)建議
所有屬性都聲明為 nonatomic
盡量避免多線程搶奪同一塊資源
盡量將加鎖粘秆、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理,減小移動(dòng)客戶端的壓力
4.9收毫、線程間通訊
主線程實(shí)現(xiàn)
1攻走、定義屬性
/// 根視圖是滾動(dòng)視圖
@property (nonatomic, strong) UIScrollView*scrollView;
/// 圖像視圖
@property (nonatomic, weak) UIImageView *imageView;
/// 網(wǎng)絡(luò)下載的圖像
@property (nonatomic, weak) UIImage *image;
2、loadView 方法
加載視圖層次結(jié)構(gòu)
用純代碼開(kāi)發(fā)應(yīng)用程序時(shí)使用
功能和 Storyboard & XIB 是等價(jià)的
- (void)loadView {
_scrollView = [[UIScrollView alloc] init];
_scrollView.backgroundColor = [UIColor orangeColor];
self.view = _scrollView;
UIImageView *iv = [[UIImageView alloc] init];
[self.view addSubview:iv];
_imageView = iv;
}
3此再、viewDidLoad 方法
視圖加載完成后執(zhí)行
可以做一些數(shù)據(jù)初始化的工作
如果用純代碼開(kāi)發(fā)昔搂,不要在此方法中設(shè)置界面 UI
- (void)viewDidLoad {
[super viewDidLoad];
// 下載圖像
[self downloadImage];
}
4、下載網(wǎng)絡(luò)圖片
- (void)downloadImage{
// 1. 網(wǎng)絡(luò)圖片資源路徑
NSURL *url = [NSURL URLWithString:@"[http://c.hiphotos.baidu.com/image/pic/item/4afbfbedab64034f42b14da1aec379310a551d1c.jpg](http://c.hiphotos.baidu.com/image/pic/item/4afbfbedab64034f42b14da1aec379310a551d1c.jpg)"];
// 2. 從網(wǎng)絡(luò)資源路徑實(shí)例化二進(jìn)制數(shù)據(jù)(網(wǎng)絡(luò)訪問(wèn))
NSData *data = [NSData dataWithContentsOfURL:url];
// 3. 將二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成圖像
UIImage *image = [UIImage imageWithData:data];
// 4. 設(shè)置圖像
self.image = image;
}
5输拇、設(shè)置圖片
- (void)setImage:(UIImage *)image {
// 1. 設(shè)置圖像視圖的圖像
self.imageView.image = image;
// 2. 按照?qǐng)D像大小設(shè)置圖像視圖的大小
[self.imageView sizeToFit];
// 3. 設(shè)置滾動(dòng)視圖的 contentSize
self.scrollView.contentSize = image.size;
}
6摘符、設(shè)置滾動(dòng)視圖的縮放
設(shè)置滾動(dòng)視圖縮放屬性
// 1> 最小縮放比例
self.scrollView.minimumZoomScale = 0.5;
// 2> 最大縮放比例
self.scrollView.maximumZoomScale = 2.0;
// 3> 設(shè)置代理
self.scrollView.delegate = self;
實(shí)現(xiàn)代理方法 - 告訴滾動(dòng)視圖縮放哪一個(gè)視圖
#pragma mark - UIScrollViewDelegate 代理方法
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return self.imageView;
}
7、線程間通訊
在后臺(tái)線程下載圖像
[self performSelectorInBackground:@selector(downloadImage) withObject:nil];
在主線程設(shè)置圖像
// waitUntilDone:是否等待主線程執(zhí)行完畢 setImage:方法。
// YES:等待 NO:不等待
// 一般不用等待逛裤,直接設(shè)置 NO 即可
[self performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];