iOS 多線程(三)NSThread的使用

一敬尺、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 _成員變量 就不自動生成。實際上原子屬性內部有一個鎖别垮,叫做自旋鎖便监。

image.png

@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; 

參考博客:http://www.reibang.com/p/686dbf4bbb52

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市钳吟,隨后出現(xiàn)的幾起案子廷粒,更是在濱河造成了極大的恐慌,老刑警劉巖红且,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坝茎,死亡現(xiàn)場離奇詭異,居然都是意外死亡暇番,警方通過查閱死者的電腦和手機嗤放,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來壁酬,“玉大人次酌,你說我怎么就攤上這事∮咔牵” “怎么了岳服?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長希俩。 經(jīng)常有香客問我吊宋,道長,這世上最難降的妖魔是什么斜纪? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任贫母,我火速辦了婚禮,結果婚禮上盒刚,老公的妹妹穿的比我還像新娘腺劣。我一直安慰自己,他們只是感情好因块,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布橘原。 她就那樣靜靜地躺著,像睡著了一般涡上。 火紅的嫁衣襯著肌膚如雪趾断。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天吩愧,我揣著相機與錄音芋酌,去河邊找鬼。 笑死雁佳,一個胖子當著我的面吹牛脐帝,可吹牛的內容都是我干的同云。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼堵腹,長吁一口氣:“原來是場噩夢啊……” “哼炸站!你這毒婦竟也來了?” 一聲冷哼從身側響起疚顷,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤旱易,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后腿堤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體阀坏,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年笆檀,在試婚紗的時候發(fā)現(xiàn)自己被綠了全释。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡误债,死狀恐怖,靈堂內的尸體忽然破棺而出妄迁,到底是詐尸還是另有隱情寝蹈,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布登淘,位于F島的核電站箫老,受9級特大地震影響,放射性物質發(fā)生泄漏黔州。R本人自食惡果不足惜耍鬓,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望流妻。 院中可真熱鬧牲蜀,春花似錦、人聲如沸绅这。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽证薇。三九已至度苔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間浑度,已是汗流浹背寇窑。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留箩张,地道東北人甩骏。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓窗市,卻偏偏與公主長得像,于是被迫代替她去往敵國和親横漏。 傳聞我的和親對象是個殘疾皇子谨设,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內容