NSThread是蘋果官方提供的,使用OC代碼編寫,使用起來(lái)比
pthread
更加面向?qū)ο螅?jiǎn)單易用又固,可以直接操作線程對(duì)象仲器,需要我們手動(dòng)管理線程的生命周期。NSThread是一個(gè)基于pthreads
使用OC代碼封裝.
關(guān)于NSThread
相關(guān)API我這里會(huì)結(jié)合對(duì)應(yīng)功能使用做些說(shuō)明仰冠。詳細(xì)的API說(shuō)明可參考官方文檔
NSThread的創(chuàng)建
使用NSThread
該類創(chuàng)建線程有兩種方法:
- 使用
detachNewThreadSelector:toTarget:withObject:
class方法生成新線程乏冀。 - 創(chuàng)建一個(gè)新NSThread對(duì)象并調(diào)用其start方法。(僅在iOS和OS X v10.5及更高版本中受支持).
這兩個(gè)方法都會(huì)在應(yīng)用程序中創(chuàng)建一個(gè)分離的線程洋只。線程退出時(shí)系統(tǒng)會(huì)自動(dòng)回收線程的資源辆沦。
detachNewThreadSelector:toTarget:withObject:OS X的所有版本都支持該方法.提供要用作線程入口點(diǎn)的方法名稱(選擇器)昼捍,定義該方法的對(duì)象以及要在啟動(dòng)時(shí)傳遞給線程的任何數(shù)據(jù)。
使用實(shí)例:
[NSThread detachNewThreadSelector:@selector(threadRun:) toTarget:self withObject:@"使用detachNewThreadSelector開(kāi)啟子線程"];
- (void)threadRun:(NSString *)param {
NSLog(@"----threadRun:%@ ----%@",[NSThread currentThread],param);
}
NSThread
在OS Xv10.5的更高及版本中初始化對(duì)象的簡(jiǎn)單方法可使用initWithTarget:selector:object:方法肢扯。但是妒茬,它不會(huì)啟動(dòng)該線程。要啟動(dòng)該線程蔚晨,用start顯式調(diào)用線程對(duì)象的方法
使用該initWithTarget:selector:object:方法的
還可以子類繼承NSThread并覆蓋其main
方法乍钻。用此方法的重寫實(shí)現(xiàn)線程的入口更多操作。
使用實(shí)例
//1.alloc init 創(chuàng)建線程铭腕,需要手動(dòng)啟動(dòng) 僅在iOS和OS X v10.5及更高版本中受支持
//創(chuàng)建一個(gè)新NSThread對(duì)象并調(diào)用其start方法银择,線程退出時(shí)系統(tǒng)會(huì)自動(dòng)回收線程的資源
- (void)createNSThreadA {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun:) object:@"使用initWithTarget開(kāi)啟NSThread子線程"];
[thread start];
}
第三種 還可以使用線程通信的方式,使用performselector
相關(guān)方法開(kāi)啟子線程
例如可以用performSelectorInBackground:withObject:開(kāi)啟后臺(tái)線程累舷。
使用實(shí)例
[self performSelectorInBackground:@selector(threadRun:) withObject:@"使用perform開(kāi)始子線程"];
NSthread 線程一些屬性說(shuō)明
配置線程堆棧大小
默認(rèn)情況下 子線程 堆棧大小512KB左右浩考,iOS主線程1M左右空間大小,
子線線程允許的最小堆棧大小為8 KB被盈,堆棧大小必須為4 KB的倍數(shù)析孽。
子線程堆棧大小我們可以通過(guò)代碼控制。
在iOS和OS X v10.5及更高版本中害捕,分配并初始化NSThread對(duì)象(不要使用該detachNewThreadSelector:toTarget:withObject:
方法)绿淋。在調(diào)用start
線程對(duì)象的方法之前使用setStackSize:方法指定新的堆棧大小。在線程啟動(dòng)后設(shè)置堆棧大小會(huì)更改屬性大小尝盼,但不會(huì)影響為線程預(yù)留的實(shí)際堆棧大小吞滞。
如果設(shè)置setStackSize:小于8KB或者不是4KB倍數(shù),系統(tǒng)都會(huì)拋出類似異常:
it must be a multiple of the system page size and greater than 8192
配置線程局部存儲(chǔ)鍵值對(duì)
每個(gè)線程都維護(hù)了一個(gè)鍵值對(duì)字典盾沫,可以再線程任何位置訪問(wèn)裁赠。可以使用
NSTread
對(duì)象屬性threadDictionary存儲(chǔ)要在整個(gè)線程執(zhí)行期間保留的信息赴精。
設(shè)置線程優(yōu)先級(jí)
使用
NSThread
的類方法setThreadPriority:;
傳入的參數(shù)是double
類型需要在0.0~1.0范圍之間佩捞,默認(rèn)是0.5
.線程優(yōu)先級(jí)越高,CPU調(diào)度的該線程的頻率會(huì)越高蕾哟。優(yōu)先級(jí)較高的線程比具有較低優(yōu)先級(jí)的線程更可能運(yùn)行一忱。較高優(yōu)先級(jí)并不能保證線程的特定執(zhí)行時(shí)間,只是與較低優(yōu)先級(jí)的線程相比谭确,調(diào)度程序更有可能選擇它帘营。
NSThread線程生命周期
相關(guān)函數(shù)
啟動(dòng)線程 進(jìn)入就緒->運(yùn)行狀態(tài)。任務(wù)執(zhí)行完畢自行銷毀
阻塞線程,進(jìn)入阻塞狀態(tài)
+ (void)sleepUntilDate:(NSDate *)date
+ (void)sleepForTimeInterval:(NSTimeInterval)ti
更改接收器的取消狀態(tài)以指示它應(yīng)該退出.
取消線程并不會(huì)馬上停止并退出線程逐哈,僅僅用作(線程是否需要退出)狀態(tài)記錄
然后通過(guò)調(diào)用@property(readonly, getter=isCancelled) BOOL cancelled獲取是否取消的狀態(tài)然后做相關(guān)操作芬迄。
終止當(dāng)前線程
建議不要使用此方法。殺死一個(gè)線程可以防止該線程自行清理昂秃。線程分配的內(nèi)存可能會(huì)被泄露禀梳,并且線程當(dāng)前正在使用的任何其他資源可能無(wú)法正確清理杜窄,從而產(chǎn)生潛在問(wèn)題。
要在操作過(guò)程中終止線程算途,一開(kāi)始就設(shè)計(jì)線程以響應(yīng)取消或退出消息塞耕。調(diào)用- (void)cancel。再根@property(readonly, getter=isCancelled) BOOL cancelled狀態(tài)來(lái)是否調(diào)用+(void)exit郊艘。這樣線程將有機(jī)會(huì)執(zhí)行任何所需的清理并正常退出荷科。
還可以通過(guò)運(yùn)行循環(huán)輸入源來(lái)控制線程是否退出.這個(gè)涉及到runloop,后面我會(huì)研究纱注。有興趣可先看官方文檔說(shuō)明畏浆。
其實(shí)取消終止線程還有個(gè)快捷方法,如果使用detachNewThreadSelector:toTarget:withObject:
class方法生成新線程狞贱】袒瘢可以直接使用類方法+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument
快速取消一個(gè)線程。
線程安全和同步
如果多個(gè)線程訪問(wèn)統(tǒng)一資源,修改相同資源的兩個(gè)線程可能會(huì)以非預(yù)期的方式相互干擾瞎嬉。例如蝎毡,一個(gè)線程可能會(huì)覆蓋另一個(gè)線程的更改,或者將應(yīng)用程序置于未知且可能無(wú)效的狀態(tài)氧枣。這個(gè)時(shí)候我們就需要同步工具來(lái)是線程同步沐兵,確保它們?cè)诮换r(shí)安全地進(jìn)行交互。是多條線程按順序的訪問(wèn)同一塊資源便监。
線程同步有以下幾種方式:
原子操作
原子操作是一種簡(jiǎn)單的同步形式扎谎,適用于簡(jiǎn)單的數(shù)據(jù)類型。原子操作的優(yōu)點(diǎn)是它們不會(huì)阻止競(jìng)爭(zhēng)線程烧董。對(duì)于簡(jiǎn)單的操作毁靶,例如遞增計(jì)數(shù)器變量,這可以帶來(lái)比獲取鎖定更好的性能.可以對(duì)32位或64位值執(zhí)行簡(jiǎn)單的數(shù)學(xué)和邏輯運(yùn)算逊移。這些操作依賴于特殊的硬件指令,以確保在再次訪問(wèn)受影響的內(nèi)存之前完成給定的操作.使用需要導(dǎo)入頭文件
<libkern/OSAtomic.h>
.
相關(guān)API參考atomic预吆。
這里不做過(guò)多說(shuō)明。
鎖
鎖是iOS 最常用的同步工具之一胳泉。
iOS 鎖大概有以下幾種類型:
- 互斥鎖
- 遞歸鎖
- 讀寫鎖(共享獨(dú)占鎖)
- 分布式鎖
- 自旋鎖
- 雙重鎖
這里就不過(guò)多討論拐叉。后面我會(huì)研究鎖相關(guān)的東西。
感興趣的可以看看其他人的文章iOS開(kāi)發(fā)中的11種鎖以及性能對(duì)比
這里我就@synchronized
這個(gè)互斥鎖做下簡(jiǎn)單說(shuō)明和使用實(shí)例
@synchronized
是在Objective-C代碼中動(dòng)態(tài)創(chuàng)建互斥鎖的便捷方式
使用如下:
@synchronized(Obj)
{
//大括號(hào)之間的所有內(nèi)容都受@synchronized指令保護(hù)扇商。
}
傳遞給@synchronized
指令的對(duì)象obj
是用于區(qū)分受保護(hù)塊的唯一標(biāo)識(shí)符凤瘦。如果在兩個(gè)不同的線程中執(zhí)行上述方法,則obj
在每個(gè)線程上為參數(shù)傳遞一個(gè)不同的對(duì)象钳吟,每個(gè)線程都會(huì)鎖定并繼續(xù)處理廷粒,而不會(huì)被另一個(gè)阻塞窘拯。但是红且,如果在兩種情況下都傳遞相同的對(duì)象坝茎,則其中一個(gè)線程將首先獲取鎖定,另一個(gè)線程將阻塞暇番,直到第一個(gè)線程完成鎖定的部分嗤放。
這里就用常用的一個(gè)例子購(gòu)買車票為例子說(shuō)明線程安全與同步問(wèn)題。
- (void)createNSThreadD {
//全局變量總票數(shù)
totalCount = 100;
NSThread *threadA = [[NSThread alloc] initWithTarget:self selector:@selector(sellingTickets) object:nil];
threadA.name = @"售票員A";
[threadA start];
NSThread * threadB = [[NSThread alloc] initWithTarget:self selector:@selector(sellingTickets) object:nil];
threadB.name = @"售票員B";
[threadB start];
NSThread *threadC = [[NSThread alloc] initWithTarget:self selector:@selector(sellingTickets) object:nil];
threadC.name = @"售票員C";
[threadC start];
}
如果sellingTickets
方法類不加鎖
- (void)sellingTickets {
while (1) {
NSInteger currentCount = totalCount;
if (currentCount>0) {
//模擬耗時(shí)操作
for (NSInteger i = 0; i<1000000; i++) {
}
totalCount = currentCount-1;
NSLog(@"售票員%@售出一張票剩余%ld張票",[NSThread currentThread].name,totalCount);
}else {
NSLog(@"%@當(dāng)前票已經(jīng)售完",[NSThread currentThread].name);
break;
}
}
}
會(huì)看到如下同一時(shí)刻不同售票員售出查詢余票有沖突結(jié)果:
使用了@synchronized
鎖后完全正常
- (void)sellingTickets {
while (1) {
@synchronized (self) {
NSInteger currentCount = totalCount;
if (currentCount>0) {
//模擬耗時(shí)操作
for (NSInteger i = 0; i<1000000; i++) {
}
totalCount = currentCount-1;
NSLog(@"售票員%@售出一張票剩余%ld張票",[NSThread currentThread].name,totalCount);
}else {
NSLog(@"%@當(dāng)前票已經(jīng)售完",[NSThread currentThread].name);
break;
}
}
}
}
使用條件
條件是另一種類型的信號(hào)量壁酬,它允許線程在某個(gè)條件為真時(shí)相互發(fā)信號(hào)次酌。條件通常用于指示資源的可用性或確保以特定順序執(zhí)行任務(wù)。這里先不做過(guò)多說(shuō)明舆乔。我的上一篇文章iOS多線程學(xué)習(xí)(一)pthread對(duì)條件作了簡(jiǎn)單說(shuō)明岳服。
NSObject執(zhí)行選擇器
這個(gè)我們最熟悉,使用NSobject
的performSelector
相關(guān)函數(shù)希俩,用線程間通信實(shí)現(xiàn)實(shí)現(xiàn)線程間同步吊宋。
參考:
Threading Programming Guide
Concurrent Programming: APIs and Challenges
上一篇: