iOS中實(shí)現(xiàn)多線程的四種方案
- pthread
- NSThread
- GCD
- NSOpreation
Pthread:
這種純c語(yǔ)言的就別用了吧蜻直,還要自己管理線程的生命周期,果斷不用
NSThread:這個(gè)雖然自己管理線程的生命周期基括,但這是面向?qū)ο蟮模琌C語(yǔ)言归园,不常用到但要會(huì)用
GCD:這個(gè)很重要、很重要、很重要 自動(dòng)管理線程的生命周期 使用率非常非常頻繁
NSOpreation:這個(gè)也很重要白热,必須掌握佑稠,沒(méi)什么好說(shuō)的,也是自動(dòng)管理線程的生命周期猾编,使用率也非常高
簡(jiǎn)單了解下Pthread
只要create一次就會(huì)創(chuàng)建一個(gè)新的線程
系統(tǒng)會(huì)自動(dòng)在子線程中調(diào)用傳入的函數(shù)
/*
第一個(gè)參數(shù): 線程的代號(hào)(當(dāng)做就是線程)
第二個(gè)參數(shù): 線程的屬性
第三個(gè)參數(shù): 指向函數(shù)的指針, 就是將來(lái)線程需要執(zhí)行的方法
第四個(gè)參數(shù): 給第三個(gè)參數(shù)的指向函數(shù)的指針 傳遞的參數(shù)
一般情況下C語(yǔ)言中的類型都是以 _t或者Ref結(jié)尾
*/
pthread_t threadId;
// 只要create一次就會(huì)創(chuàng)建一個(gè)新的線程
pthread_create(&threadId , NULL, &demo, "hq");
關(guān)于NSThread
- NSThread對(duì)比后面兩種是相對(duì)輕量級(jí)的瘤睹,更直觀地控制線程對(duì)象
- 但需要自己管理線程的生命周期、同步答倡、加鎖問(wèn)題轰传,在性能上會(huì)有一定的消耗
- 注意:一個(gè)NSThread對(duì)象就代表一條線程
創(chuàng)建方法
** 第一種方法 ** (手動(dòng)開(kāi)啟線程)
// 線程一啟動(dòng),就會(huì)在線程thread中執(zhí)行self的run方法
// 1.創(chuàng)建線程
NSThread *thread = [[NSThread alloc] initWithTarget:selfselector:@selector(run) object:nil];
// 2.設(shè)置線程的優(yōu)先級(jí)(0.0 - 1.0瘪撇,1.0最高級(jí))
thread.threadPriority = 1;
// 3.啟動(dòng)線程
[thread start];
** 第二種方法 ** (自動(dòng)開(kāi)啟線程)
// detachNewThreadSelector: 不用手動(dòng)調(diào)用start方法, 系統(tǒng)會(huì)自動(dòng)啟動(dòng) 沒(méi)有返回值, 不能對(duì)線程進(jìn)行更多的設(shè)置
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
** 第三種方法 ** (隱式創(chuàng)建并啟動(dòng)線程)
// 系統(tǒng)就會(huì)自動(dòng)創(chuàng)建一個(gè)子線程, 并且在子線程中自動(dòng)執(zhí)行self的@selector方法
[self performSelectorInBackground:@selector(run) withObject:nil];
NSThread用法
- 主線程相關(guān)用法
+ (NSThread *)mainThread; // 獲得主線程
- (BOOL)isMainThread; // 是否為主線程
+ (BOOL)isMainThread; // 是否為主線程
NSThread *main = [NSThread mainThread];
- 獲得當(dāng)前線程
NSThread *current = [NSThread currentThread];
-
獲取線程的名字
- (void)setName:(NSString *)name; - (NSString *)name;
線程的狀態(tài)
- 創(chuàng)建出來(lái) -> 新建狀態(tài)
- 調(diào)用start -> 準(zhǔn)備就緒
- 被CPU調(diào)用 -> 運(yùn)行
- sleep -> 阻塞
- 執(zhí)行完畢, 或者被強(qiáng)制關(guān)閉 -> 死亡
啟動(dòng)線程
- (void)start;
// 進(jìn)入就緒狀態(tài) -> 運(yùn)行狀態(tài)获茬。當(dāng)線程任務(wù)執(zhí)行完畢,自動(dòng)進(jìn)入死亡狀態(tài)
阻塞(暫停)線程
// sleep方法是一個(gè)類方法, 所以說(shuō)明在哪個(gè)線程中調(diào)用, 就會(huì)給哪個(gè)線程設(shè)置暫時(shí)
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
// 暫停1s
[NSThread sleepForTimeInterval:1];
[NSThread sleepUntilDate:[NSDate dateWithTimeInterval:1 sinceDate:[NSDate date]]];
強(qiáng)制停止線程
// 進(jìn)入死亡狀態(tài)
+ (void)exit;
注意:一旦線程停止(死亡)了倔既,就不能再次開(kāi)啟任務(wù)
多線程的安全隱患及解決措施
- 資源共享
- 一塊資源可能會(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)題
- 解決措施
- 互斥鎖
- 互斥鎖的使用前提:多條線程搶奪同一塊資源
- 注意:鎖定1份代碼只用1把鎖实蓬,用多把鎖是無(wú)效的
- 使用格式
// (鎖對(duì)象self)
@synchronized
{
// 需要鎖定的代碼
}
/*
只要被@synchronized的{}包裹起來(lái)的代碼, 同一時(shí)刻就只能被一個(gè)線程執(zhí)行
注意:
1. 只要枷鎖就會(huì)消耗性能
2. 加鎖必須傳遞一個(gè)對(duì)象, 作為鎖
3. 如果想真正的鎖住代碼, 那么多個(gè)線程必須使用同一把鎖才行
4. 加鎖的時(shí)候盡量縮小范圍, 因?yàn)榉秶酱笮阅芫驮降? */
-
互斥鎖的優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):能有效防止因多線程搶奪資源造成的數(shù)據(jù)安全問(wèn)題
- 缺點(diǎn):需要消耗大量的CPU資源
-
線程同步
- 線程同步的意思是:多條線程在同一條線上執(zhí)行(按順序地執(zhí)行任務(wù))
互斥鎖茸俭,就是使用了線程同步技術(shù)
- 線程同步的意思是:多條線程在同一條線上執(zhí)行(按順序地執(zhí)行任務(wù))
原子和非原子屬性
- OC在定義屬性時(shí)有nonatomic和atomic兩種選擇
- atomic:原子屬性,為setter方法加鎖(默認(rèn)就是atomic)
- nonatomic:非原子屬性安皱,不會(huì)為setter方法加鎖
- 原子和非原子屬性的選擇
- atomic:線程安全调鬓,需要消耗大量的資源
- nonatomic:非線程安全,適合內(nèi)存小的移動(dòng)設(shè)備
- iOS開(kāi)發(fā)的建議
- 所有屬性都聲明為nonatomic
- 盡量避免多線程搶奪同一塊資源
- 盡量將加鎖练俐、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理袖迎,減小移動(dòng)客戶端的壓力
注意點(diǎn): atomic系統(tǒng)自動(dòng)給我們添加的鎖不是互斥鎖/ 自旋鎖
- 自旋鎖和互斥鎖對(duì)比
- 相同點(diǎn):都能夠保證多線程在同一時(shí)候, 只能有一個(gè)線程操作鎖定的代碼
- 不同點(diǎn)
- 如果是互斥鎖, 假如現(xiàn)在被鎖住了, 那么后面來(lái)得線程就會(huì)進(jìn)入”休眠”狀態(tài), 直到解鎖之后, 又會(huì)喚醒線程繼續(xù)執(zhí)行
- 如果是自旋鎖, 假如現(xiàn)在被鎖住了, 那么后面來(lái)得線程不會(huì)進(jìn)入休眠狀態(tài), 會(huì)一直傻傻的等待, 直到解鎖之后立刻執(zhí)行
- 自旋鎖更適合做一些較短的操作
線程間的通信
-
什么叫做線程間通信
- 在1個(gè)進(jìn)程中,線程往往不是孤立存在的腺晾,多個(gè)線程之間需要經(jīng)常進(jìn)行通信
比如在主線程添加imageView燕锥,在子線程中下載圖片,然后又回到主線程中顯示圖片
- 注意點(diǎn): 更新UI一定要在主線程中更新
-
線程間通信的體現(xiàn)
- 1個(gè)線程傳遞數(shù)據(jù)給另1個(gè)線程
- 在1個(gè)線程中執(zhí)行完特定任務(wù)后悯蝉,轉(zhuǎn)到另1個(gè)線程繼續(xù)執(zhí)行任務(wù)
在當(dāng)前線程
[self performSelector:@selector(run) withObject:nil];
-
在主線程
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];
在指定線程
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES];
// waitUntilDone的含義: 如果傳入的是YES: 那么會(huì)等到主線程中的方法執(zhí)行完畢, 才會(huì)繼續(xù)執(zhí)行下面其他行的代碼 如果傳入的是NO: 那么不用等到主線程中的方法執(zhí)行完畢, 就可以繼續(xù)執(zhí)行下面其它行的代碼
線程間的通信最常見(jiàn)的就是在子線程中做耗時(shí)操作归形,然后回到主線程刷新UI