1. 程式人生 > >iOS 多執行緒(二)NSThread

iOS 多執行緒(二)NSThread

iOS 使用NSThread來代表執行緒,建立新執行緒也就是建立一個NSThread物件。

1 建立和啟動執行緒

iOS10之前提供了兩種方法開啟執行緒。

- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0);
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
第一種方法是例項方法,返回一個NSThread物件,必須呼叫start
方法來啟動執行緒。

第二種方法是類方法,不會返回NSThread物件,直接建立並啟動執行緒。

iOS10增加了兩種建立啟動執行緒的方法

- (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+ (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
這兩個方法與上述類似,也是一個例項方法,一個類方法。只是執行方法體不一樣了。
具體使用如下:
#import "ZPYViewController.h"

@interface ZPYViewController ()

@end

@implementation ZPYViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    [self makeThread1beforeIOS10];
    [self makeThread2beforeIOS10];
    [self makeThread1FromIOS10];
    [self makeThread2FromIOS10];
}
-(void)run{
    for(int i=0;i<10;i++){
        NSLog(@"%@,----i=%d",[NSThread currentThread],i);
        NSLog(@"isMainThread=%d",[NSThread isMainThread]);
    }
}

//iOS 10 之前NSThread兩種方法建立執行緒。如下:
-(void) makeThread1beforeIOS10{
    //第一種建立方式 例項方法 最多可以傳一個引數
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [thread1 setName:@"fisrt blood"];
    //啟動執行緒
    [thread1 start];
    BOOL isMain = [NSThread isMainThread];
    NSLog(@"isMain=%d,thread1=%d",isMain,[thread1 isMainThread]);
}
//第二種建立方式 類方法。 最多可以傳一個引數
-(void) makeThread2beforeIOS10{
    //類方法,不會返回NSThread物件,直接啟動執行緒
    [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
}
//iOS 10又添加了兩個方法建立執行緒。
// 需要iOS 10 才可以執行下面兩個方法。否則會出錯。
//第三種建立方式 例項方法
-(void) makeThread1FromIOS10{
    NSThread *thread3 = [[NSThread alloc] initWithBlock:^{
        for(int i=0;i<10;i++){
            NSLog(@"%@,第三種 i=%d",[NSThread currentThread],i);
        }
    }];
    thread3.name = @"triple kill";
    //呼叫start方法啟動執行緒
    [thread3 start];
}
//第四種建立方式 類方法
-(void) makeThread2FromIOS10{
    //類方法,不會返回NSThread物件,直接啟動執行緒
    [NSThread detachNewThreadWithBlock:^{
        for(int i=0;i<10;i++){
            NSLog(@"%@,第四種 i=%d",[NSThread currentThread],i);
        }
    }];
}
@end

2 常用方法

主執行緒相關:

+ (NSThread *)mainThread; // 獲得主執行緒
- (BOOL)isMainThread; // 是否為主執行緒
+ (BOOL)isMainThread; // 是否為主執行緒
其他方法:

獲得當前執行緒

NSThread *current = [NSThreadcurrentThread];

執行緒的排程優先順序

+ (double)threadPriority;

+ (BOOL)setThreadPriority:(double)p;

- (double)threadPriority;

- (BOOL)setThreadPriority:(double)p;

排程優先順序的取值範圍是0.0 ~ 1.0,預設0.5,值越大,優先順序越高

自己開發時,建議一般不要修改優先順序

執行緒的名字

- (void)setName:(NSString *)n;

- (NSString *)name;


3 執行緒的狀態

NSThread *thread1 = [[NSThreadalloc]initWithTarget:selfselector:@selector(run)object:nil];

//啟動執行緒

[thread1 start];


五種狀態:新建、就緒、執行、阻塞、死亡

新建:當程式建立一個執行緒後,該執行緒就處於新建狀態,此時它和其他OC物件一樣,僅僅有系統分配了記憶體,初始化了成員變數。

就緒:當執行緒物件呼叫start方法後,該執行緒處於就緒狀態。處於該狀態的執行緒並沒有開始執行,至於何時執行,取決於系統的排程。

執行:當系統排程到該執行緒時,進入執行狀態。呼叫到其他執行緒則該執行緒就又重新回到就緒狀態。

阻塞:當執行緒呼叫了sleep方法或等待同步鎖時進入阻塞狀態。

死亡:當執行緒執行完畢,或異常/強制退出,則該執行緒進入死亡狀態。

啟動執行緒

- (void)start; //進入就緒狀態-> 執行狀態。當執行緒任務執行完畢,自動進入死亡狀態

阻塞(暫停)執行緒

+ (void)sleepUntilDate:(NSDate *)date;

+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

// 進入阻塞狀態

強制停止執行緒

+ (void)exit;

// 進入死亡狀態

注意:一旦執行緒停止(死亡)了,就不能再次開啟任務

4 停止執行緒

有時我們需要將子執行緒結束,這該怎麼辦呢?

執行緒結束方式又三種

1 執行緒執行體方法執行完成,執行緒正常結束。

2 執行緒執行過程中發生了異常。

3 呼叫NSThread的exit方法類停止當前正在執行的執行緒。

由於exit方法為類方法,所以只能停止正在執行的執行緒。

我們可以在主執行緒或者其他執行緒中呼叫[thread cancel]。而子執行緒的執行體中可以判斷是否cancel來停止執行緒。

//結束執行緒
    if([[NSThread currentThread] isCancelled]){
        [NSThread exit];
    }

5 多執行緒安全

在多執行緒中,必然會涉及到執行緒安全點問題,比如資源共享(多個執行緒訪問同一個物件、同一個變數、同一個檔案),這時很容易發生資料錯亂、資料安全問題。

如下經典問題,取錢


如上圖:1500取出1600,還剩餘700,這樣顯然是不合理的(雖然對於使用者來說很樂意了)。

-(void)takeMoney:(NSNumber *)count{
    [_account take:count.doubleValue];
}
- (IBAction)actionTest:(id)sender {
    _account = [[ZPYAccount alloc] initWithCardId:@"1234567" andBalance:1500];
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(takeMoney:) object:[NSNumber numberWithInt:800]];
    [thread1 setName:@"first thread"];
    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(takeMoney:) object:[NSNumber numberWithInt:800]];
    [thread2 setName:@"second thread"];
    [thread1 start];
    [thread2 start];
}
兩個執行緒去做取錢的操作。

ZPYAccount中的take方法:

-(void)take:(double)count{
    double money = _balance;
    if(count <= money){
        [NSThread sleepForTimeInterval:0.001];
        _balance = money - count;
        NSLog(@"thread:%@,取錢成功,餘額為%f",[[NSThread currentThread] name],_balance);
        }else{
            NSLog(@"thread:%@,餘額不足!",[[NSThread currentThread] name]);
        }
}
輸出如下:
2016-10-11 11:40:09.052 NSThread使用[553:386755] thread:second thread,取錢成功,餘額為700.000000
2016-10-11 11:40:09.051 NSThread使用[553:386754] thread:first thread,取錢成功,餘額為700.000000

1 @synchronized實現同步:

-(void)take:(double)count{
    @synchronized (self) {
        //將需要同時執行的放入同步程式碼塊中。查錢、取錢。
        double money = _balance;
        if(count <= money){
            [NSThread sleepForTimeInterval:0.001];
            _balance = money - count;
            NSLog(@"thread:%@,取錢成功,餘額為%f",[[NSThread currentThread] name],_balance);
        }else{
            NSLog(@"thread:%@,餘額不足!",[[NSThread currentThread] name]);
        }
    }
}
列印:
2016-10-11 18:45:52.135 NSThread使用[556:387839] thread:first thread,取錢成功,餘額為700.000000
2016-10-11 18:45:52.137 NSThread使用[556:387840] thread:second thread,餘額不足!

這樣就合理了,當餘額不足時就不再讓取錢,並且餘額也沒有問題。

2 NSLock 同步鎖

使用NSLock也可以保證執行緒安全。

_lock = [[NSLockalloc]init];

-(void)takelock:(double)count{
    [_lock lock];
    double money = _balance;
    if(count <= money){
        [NSThread sleepForTimeInterval:0.001];
        _balance = money - count;
        NSLog(@"thread:%@,取錢成功,餘額為%f",[[NSThread currentThread] name],_balance);
    }else{
        NSLog(@"thread:%@,餘額不足!",[[NSThread currentThread] name]);
    }
    [_lock unlock];
}
列印:
2016-10-11 18:52:09.216 NSThread使用[559:388860] thread:first thread,取錢成功,餘額為700.000000
2016-10-11 18:52:09.217 NSThread使用[559:388861] thread:second thread,餘額不足!