一敬尺、NSThread基本介紹
??NSThread是OC中封裝程度最小最輕量級的肾请,使用更靈活留搔,基本使用比較簡單,但要手動管理線程的生命周期铛铁、線程同步和線程加鎖等隔显,開銷較大,在平時使用很少饵逐,最常用到的無非就是[NSThread currentThread]
獲取當前線程括眠。
??NSObject基類對象提供有隱式快速創(chuàng)建NSThread線程的performSelector
系列類別擴展工具方法。NSThread還提供了一些靜態(tài)工具接口來控制當前線程以及獲取當前線程的一些信息倍权。
二哺窄、NSThread初始化和屬性
1、初始化
//創(chuàng)建線程
NSThread *newThread = [[NSThread alloc]initWithTarget:self selector:@selector(demo:) object:@"Thread"];
//或者
NSThread *newThread=[[NSThread alloc]init];
NSThread *newThread= [[NSThread alloc]initWithBlock:^{
NSLog(@"initWithBlock");
}];
2、屬性
@property (class, readonly, strong) NSThread *currentThread;//當前線程
/**
每個線程都維護了一個鍵-值的字典,它可以在線程里面的任何地方被訪問萌业。
你可以使用該字典來保存一些信息,這些信息在整個線程的執(zhí)行過程中都保持不變。
比如,你可以使用它來存儲在你的整個線程過程中 Run loop 里面多次迭代的狀態(tài)信息奸柬。
NSThread實例可以使用一下方法
*/
@property (readonly, retain) NSMutableDictionary *threadDictionary;
NSMutableDictionary *dict = [thread threadDictionary];
//優(yōu)先級
@property double threadPriority ;
/** NSQualityOfService:
NSQualityOfServiceUserInteractive:最高優(yōu)先級,主要用于提供交互UI的操作,比如處理點擊事件,繪制圖像到屏幕上
NSQualityOfServiceUserInitiated:次高優(yōu)先級生年,主要用于執(zhí)行需要立即返回的任務
NSQualityOfServiceDefault:默認優(yōu)先級,當沒有設置優(yōu)先級的時候廓奕,線程默認優(yōu)先級
NSQualityOfServiceUtility:普通優(yōu)先級抱婉,主要用于不需要立即返回的任務
NSQualityOfServiceBackground:后臺優(yōu)先級,用于完全不緊急的任務
*/
@property NSQualityOfService qualityOfService;
@property (nullable, copy) NSString *name;//線程名稱
@property NSUInteger stackSize ;//線程使用棧區(qū)大小桌粉,默認是512K
@property (readonly, getter=isExecuting) BOOL executing;//線程是否在執(zhí)行
@property (readonly, getter=isFinished) BOOL finished;//線程是否執(zhí)行結束
@property (readonly, getter=isCancelled) BOOL cancelled;//線程是否取消
三蒸绩、實例方法
- (void)start;//啟動線程
- (BOOL)isMainThread;//是否為主線程
- (void)setName:(NSString *)n;//設置線程名稱
- (void)cancel ;//取消線程
- (void)main ;//線程的入口函數(shù)
- (void)isExecuting;//判斷線程是否正在執(zhí)行
- (void)isFinished;//判斷線程是否已經(jīng)完成
- (void)isCancelled; //判斷線程是否取消
四、類方法
+ (void)detachNewThreadWithBlock:(void (^)(void))block;//block方式
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;//SEL方式
+ (void)currentThread;//獲取當前線程
+ (BOOL)isMultiThreaded;//當前代碼運行所在線程是否是子線程
+ (void)sleepUntilDate:(NSDate *)date;//當前代碼所在線程睡到指定時間
+ (void)sleepForTimeInterval:(NSTimeInterval)ti; //當前線程睡多長時間
+ (void)exit;//退出當前線程
+ (double)threadPriority;//設置當前線程優(yōu)先級
//給當前線程設定優(yōu)先級铃肯,調度優(yōu)先級的取值范圍是0.0 ~ 1.0患亿,默認0.5,值越大押逼,優(yōu)先級越高步藕。
+ (BOOL)setThreadPriority:(double)p;
//線程的調用都會有函數(shù)的調用函數(shù)的調用就會有棧返回地址的記錄,在這里返回的是函 數(shù)調用返回的虛擬地址挑格,說白了就是在該線程中函數(shù)調用的虛擬地址的數(shù)組
+ (NSArray *)callStackReturnAddresses;
//同上面的方法一樣咙冗,只不過返回的是該線程調用函數(shù)的名字數(shù)字
+ (NSArray *)callStackSymbols;
五、隱式創(chuàng)建&線程間通訊
??以下方法位于NSObject (NSThreadPerformAdditions)分類中漂彤,所有繼承NSObject 實例化對象都可調用以下方法
/**
指定方法在主線程中執(zhí)行
參數(shù):
1. SEL 方法
2.方法參數(shù)
3.是否等待當前執(zhí)行完畢
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
/**
指定方法在某個線程中執(zhí)行
參數(shù):
1. SEL 方法
2.方法參數(shù)
3.是否等待當前執(zhí)行完畢
4.指定的Runloop model
*/
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
// equivalent to the first method with kCFRunLoopCommonModes
/**
指定方法在開啟的子線程中執(zhí)行
參數(shù):
1. SEL 方法
2.方法參數(shù)
*/
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
??注意:
我們經(jīng)常提到的線程間通訊
其實就是上面幾個方法雾消,并不是多高大上,也沒有多復雜4焱A⑷蟆!同時蘋果聲明UI更新一定要在UI線程(主線程)中執(zhí)行士骤,雖然不是所有后臺線程更新UI都會出錯范删。
六、線程間資源共享&線程加鎖
??在程序運行過程中拷肌,如果存在多線程到旦,那么各個線程讀寫資源就會存在先后、同時讀寫資源的操作巨缘,因為是在不同線程添忘,CPU調度過程中我們無法保證哪個線程會先讀寫資源,哪個線程后讀寫資源若锁。因此為了防止數(shù)據(jù)讀寫混亂和錯誤的發(fā)生搁骑,我們要將線程在讀寫數(shù)據(jù)時加鎖,這樣就能保證操作同一個數(shù)據(jù)對象的線程只有一個,當這個線程執(zhí)行完成之后解鎖仲器,其他的線程才能操作此數(shù)據(jù)對象煤率。NSLock / NSConditionLock / NSRecursiveLock / @synchronized都可以實現(xiàn)線程上鎖的操作。
1乏冀、@synchronized
直接上例子:相信12306賣火車票的例子大家了解
首先:開啟兩個線程同時售票
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) {
[NSThread sleepForTimeInterval:1.0];
//互斥鎖 -- 保證鎖內的代碼在同一時間內只有一個線程在執(zhí)行
@synchronized (self) {
//1.判斷是否有票
if (self.tickets > 0) {
//2.如果有就賣一張
self.tickets --;
NSLog(@"還剩%d張票 %@",self.tickets,[NSThread currentThread]);
} else {
//3.沒有票了提示
NSLog(@"賣完了 %@",[NSThread currentThread]);
break;
}
}
}
}
2蝶糯、NSLock
-(BOOL)tryLock;//嘗試加鎖,成功返回YES 辆沦;失敗返回NO 昼捍,但不會阻塞線程的運行
/** 在指定的時間以前得到鎖。
YES:在指定時間之前獲得了鎖肢扯;
NO:在指定時間之前沒有獲得鎖妒茬。
該線程將被阻塞,直到獲得了鎖蔚晨,或者指定時間過期乍钻。
*/
-(BOOL)lockBeforeDate:(NSDate *)limit;
- (void)setName:(NSString*)newName//為鎖指定一個Name
- (NSString*)name//**返回鎖指定的**name
@property (nullable, copy) NSString *name;線程鎖名稱
舉個例子:
NSLock* myLock=[[NSLock alloc]init];
NSString *str=@"hello";
[NSThread detachNewThreadWithBlock:^{
[myLock lock];
NSLog(@"%@",str);
str=@"world";
[myLock unlock];
}];
[NSThread detachNewThreadWithBlock:^{
[myLock lock];
NSLog(@"%@",str);
str=@"變化了";
[myLock unlock];
}];
輸出結果不加鎖之前,兩個線程輸出一樣 hello蛛株;加鎖之后团赁,輸出分辨為hello 與world。
3谨履、NSConditionLock
使用此鎖欢摄,在線程沒有獲得鎖的情況下,阻塞笋粟,即暫停運行怀挠,典型用于生產(chǎn)者/消費者模型。
- (instancetype)initWithCondition:(NSInteger)condition;//初始化條件鎖
- (void)lockWhenCondition:(NSInteger)condition;//加鎖 (條件是:鎖空閑害捕,即沒被占用绿淋;條件成立)
- (BOOL)tryLock; //嘗試加鎖,成功返回TRUE尝盼,失敗返回FALSE
- (BOOL)tryLockWhenCondition:(NSInteger)condition;//在指定條件成立的情況下嘗試加鎖吞滞,成功返回TRUE,失敗返回FALSE
- (void)unlockWithCondition:(NSInteger)condition;//在指定的條件成立時盾沫,解鎖
- (BOOL)lockBeforeDate:(NSDate *)limit;//在指定時間前加鎖裁赠,成功返回TRUE,失敗返回FALSE赴精,
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;//條件成立的情況下佩捞,在指定時間前加鎖,成功返回TRUE蕾哟,失敗返回FALSE一忱,
@property (readonly) NSInteger condition;//條件鎖的條件
@property (nullable, copy) NSString *name;//條件鎖的名稱
舉個例子:
NSConditionLock* myCondition=[[NSConditionLock alloc]init];
[NSThread detachNewThreadWithBlock:^{
for(int i=0;i<5;i++) {
[myCondition lock];
NSLog(@"當前解鎖條件:%d",i);
sleep(2);
[myCondition unlockWithCondition:i];
BOOL isLocked=[myCondition tryLockWhenCondition:2];
if (isLocked) {
NSLog(@"加鎖成功A!A庇F鼻!");
[myCondition unlock];
}
}
}];
輸出結果仪吧,在條件2 解鎖之后庄新,等待條件2 的鎖加鎖成功。
4薯鼠、NSRecursiveLock
此鎖可以在同一線程中多次被使用,但要保證加鎖與解鎖使用平衡械蹋,多用于遞歸函數(shù)出皇,防止死鎖。
- (BOOL)tryLock;//嘗試加鎖哗戈,成功返回TRUE郊艘,失敗返回FALSE
- (BOOL)lockBeforeDate:(NSDate *)limit;//在指定時間前嘗試加鎖,成功返回TRUE唯咬,失敗返回FALSE
@property (nullable, copy) NSString *name;//線程鎖名稱
使用示例:
- (void)initRecycle:(int)value {
[myRecursive lock];
if (value > 0) {
NSLog(@"當前的value值:%d",value);
sleep(2);
[self initRecycle:value - 1];
}
[myRecursive unlock];
}
輸出結果: 從你傳入的數(shù)值一直到1纱注,不會出現(xiàn)死鎖
七、線程安全之原子屬性 atomic
1胆胰、原子屬性
(線程安全)與非原子屬性
狞贱,平時我們 @property
聲明對象屬性時會用到nonatomic
2、蘋果系統(tǒng)在我們聲明對象屬性時默認是atomic
蜀涨,也就是說在讀寫這個屬性的時候瞎嬉,保證同一時間內只有一個線程能夠執(zhí)行。當聲明時用的是atomic
厚柳,通常會生成 _成員變量
氧枣,如果同時重寫了getter&setter _成員變量
就不自動生成。實際上原子屬性
內部有一個鎖别垮,叫做自旋鎖
便监。
@property (strong, nonatomic) NSObject *myNonatomic;
@property (strong, atomic) NSObject *myAtomic;
根據(jù)上面描述,我們得出結論碳想,當我們重寫了myAtomic的setter和getter方法
- (void)setMyAtomic:(NSObject *)myAtomic{
_myAtomic = myAtomic;
}
- (NSObject *)myAtomic{
return _myAtomic;
}
那么我們就必須聲明一個_myAtomic靜態(tài)變量
@synthesize myAtomic = _myAtomic;
否則系統(tǒng)在編譯的時候找不到 _myAtomic
八烧董、子線程上的Runloop
1、Runloop基本介紹(runloop階段詳細講解):
Runloop
:ios運行循環(huán)機制
目的
:保證程序不退出
監(jiān)聽事件
:沒有事件讓程序進入休眠
區(qū)分模式
:NSDefaultRunLoopMode - 時鐘移袍、網(wǎng)絡事件等
void click(int type){
printf("正在運行第%d",type);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
while (YES) {
printf("請輸入選項 0 表示退出");
int result = -1;
scanf("%d",&result);
if (result == 0) {
printf("程序結束\n");
break;
}else{
click(result);
}
}
}
return 0;
}
2解藻、在iOS中,子線程上的Runloop
是默認不開啟
的葡盗,并且子線程中的Runloop
開啟之后是手動無法關閉
的螟左,同時即使開啟之后如果不添加任何任務啡浊,Runloop
會進入休眠狀態(tài),獲取不到當前的Runloop
@property (assign, nonatomic, getter=isFinished) BOOL finished;
創(chuàng)建子線程并添加任務
NSThread *t = [[NSThread alloc]initWithTarget:self selector:@selector(demo) object:nil];
[t start];
self.finished = NO;
[self performSelector:@selector(otherMethod) onThread:t withObject:nil waitUntilDone:NO];
在第一個任務中加入死循環(huán)
- (void)demo{
NSLog(@"%@",[NSThread currentThread]);
//在OC中使用比較多的胶背,退出循環(huán)的方式
while (!self.isFinished) {
[[NSRunLoop currentRunLoop]runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
}
NSLog(@"能來嗎巷嚣?");
}
在最后添加的任務結束后結束死循環(huán)
- (void)otherMethod{
for (int i = 0; i < 10; i ++) {
NSLog(@"%s %@",__FUNCTION__,[NSThread currentThread]);=
}
//讓上面方法中的死循環(huán)結束
self.finished = YES;