1. 程式人生 > >iOS多執行緒篇:NSThread簡單介紹和使用

iOS多執行緒篇:NSThread簡單介紹和使用

一、什麼是NSThread

NSThread是基於執行緒使用,輕量級的多執行緒程式設計方法(相對GCD和NSOperation),一個NSThread物件代表一個執行緒,

需要手動管理執行緒的生命週期,處理執行緒同步等問題。

二、NSThread方法介紹

1)動態建立

1 NSThread * newThread = [[NSThread alloc]initWithTarget:self
selector:@selector(threadRun) object:nil];

動態方法返回一個新的thread物件,需要呼叫start方法來啟動執行緒

2)靜態建立

1 [NSThread detachNewThreadSelector:@selector(threadRun) toTarget:self withObject:nil];

由於靜態方法沒有返回值,如果需要獲取新建立的thread,需要在selector中呼叫獲取當前執行緒的方法

3)執行緒開啟

1 [newThread start];

4)執行緒暫停

1 2 [NSThread sleepForTimeInterval:1.0]; (以暫停一秒為例) [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];

NSThread的暫停會有阻塞當前執行緒的效果

5)執行緒取消

1 [newThread cancel];

取消執行緒並不會馬上停止並退出執行緒,僅僅只作(執行緒是否需要退出)狀態記錄

6)執行緒停止

1 [NSThread exit];

停止方法會立即終止除主執行緒以外所有執行緒(無論是否在執行任務)並退出,需要在掌控所有執行緒狀態的情況下呼叫此方法,

否則可能會導致記憶體問題。

7)獲取當前執行緒

1 [NSThread currentThread];

8)獲取主執行緒

1 [NSThread mainThread];

9)執行緒優先順序設定

iOS 8以前使用

1 [NSThread setThreadPriority:1.0];

這個方法的優先順序的數值設定讓人困惑,因為你不知道你應該設定多大的值是比較合適的,因此在iOS8之後,threadPriority添加了

一句註釋:To be deprecated; use qualityOfService below

意思就是iOS 8以後推薦使用qualityOfService屬性,通過量化的優先順序列舉值來設定

qualityOfService的列舉值如下:

  • NSQualityOfServiceUserInteractive:最高優先順序,用於使用者互動事件

  • NSQualityOfServiceUserInitiated:次高優先順序,用於使用者需要馬上執行的事件

  • NSQualityOfServiceDefault:預設優先順序,主執行緒和沒有設定優先順序的執行緒都預設為這個優先順序

  • NSQualityOfServiceUtility:普通優先順序,用於普通任務

  • NSQualityOfServiceBackground:最低優先順序,用於不重要的任務

比如給執行緒設定次高優先順序:

1 [newThread setQualityOfService:NSQualityOfServiceUserInitiated];

三、執行緒間通訊

常用的有三種:

1、指定當前執行緒執行操作

1 2 3 [self performSelector:@selector(threadRun)]; [self performSelector:@selector(threadRun) withObject:nil]; [self performSelector:@selector(threadRun) withObject:nil afterDelay:2.0];

2、(在其他執行緒中)指定主執行緒執行操作

1 [self performSelectorOnMainThread:@selector(threadRun) withObject:nil
waitUntilDone:YES];

注意:更新UI要在主執行緒中進行

3、(在主執行緒中)指定其他執行緒執行操作

1 2 [self performSelector:@selector(threadRun) onThread:newThread
withObject:nil waitUntilDone:YES];
//這裡指定為某個執行緒
[self performSelectorInBackground:@selector(threadRun) withObject:nil];
//這裡指定為後臺執行緒

四、執行緒同步

執行緒和其他執行緒可能會共享一些資源,當多個執行緒同時讀寫同一份共享資源的時候,可能會引起衝突。執行緒同步是指是指在一定的時間內只允許

某一個執行緒訪問某個資源

iOS實現執行緒加鎖有NSLock和@synchronized兩種方式。

五、執行緒的建立和使用例項:模擬售票

情景:某演唱會門票發售,在廣州和北京均開設視窗進行銷售,以下是程式碼實現

先監聽執行緒退出的通知,以便知道執行緒什麼時候退出

1 [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(threadExitNotice)
name:NSThreadWillExitNotification object:nil];

設定演唱會的門票數量

1 _ticketCount = 50;

新建兩個子執行緒(代表兩個視窗同時銷售門票)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 NSThread * window1 = [[NSThread alloc]initWithTarget:self
selector:@selector(saleTicket) object:nil];
window1.name = @"北京售票視窗"; [window1 start]; NSThread * window2 = [[NSThread alloc]initWithTarget:self
selector:@selector(saleTicket) object:nil];
window2.name = @"廣州售票視窗"; [window2 start]; 執行緒啟動後,執行saleTicket,執行完畢後就會退出,為了模擬持續售票的過程,
我們需要給它加一個迴圈
- (void)saleTicket { while (1) { //如果還有票,繼續售賣 if (_ticketCount > 0) { _ticketCount --; NSLog(@"%@", [NSString stringWithFormat:@"剩餘票數:%ld 視窗:%@", _ticketCount, [NSThread currentThread].name]); [NSThread sleepForTimeInterval:0.2]; } //如果已賣完,關閉售票視窗 else { break; } } }

執行結果:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 2016-04-06 19:25:36.637 MutiThread[4705:1371666] 剩餘票數:9 視窗:廣州售票視窗 2016-04-06 19:25:36.637 MutiThread[4705:1371665] 剩餘票數:8 視窗:北京售票視窗 2016-04-06 19:25:36.839 MutiThread[4705:1371666] 剩餘票數:7 視窗:廣州售票視窗 2016-04-06 19:25:36.839 MutiThread[4705:1371665] 剩餘票數:7 視窗:北京售票視窗 2016-04-06 19:25:37.045 MutiThread[4705:1371666] 剩餘票數:5 視窗:廣州售票視窗 2016-04-06 19:25:37.045 MutiThread[4705:1371665] 剩餘票數:6 視窗:北京售票視窗 2016-04-06 19:25:37.250 MutiThread[4705:1371665] 剩餘票數:4 視窗:北京售票視窗 2016-04-06 19:25:37.250 MutiThread[4705:1371666] 剩餘票數:4 視窗:廣州售票視窗 2016-04-06 19:25:37.456 MutiThread[4705:1371666] 剩餘票數:2 視窗:廣州售票視窗 2016-04-06 19:25:37.456 MutiThread[4705:1371665] 剩餘票數:3 視窗:北京售票視窗 2016-04-06 19:25:37.661 MutiThread[4705:1371665] 剩餘票數:1 視窗:北京售票視窗 2016-04-06 19:25:37.661 MutiThread[4705:1371666] 剩餘票數:1 視窗:廣州售票視窗 2016-04-06 19:25:37.866 MutiThread[4705:1371665] 剩餘票數:0 視窗:北京售票視窗 2016-04-06 19:25:37.867 MutiThread[4705:1371666] <nsthread: 0x7fdc91e289f0>
{number = 3, name = 廣州售票視窗} Will Exit
2016-04-06 19:25:38.070 MutiThread[4705:1371665] <nsthread: 0x7fdc91e24d60>
{number = 2, name = 北京售票視窗} Will Exit</nsthread: 0x7fdc91e24d60>
</nsthread: 0x7fdc91e289f0>

可以看到,票的銷售過程中出現了剩餘數量錯亂的情況,這就是前面提到的執行緒同步問題。

售票是一個典型的需要執行緒同步的場景,由於售票渠道有很多,而票的資源是有限的,當多個渠道在短時間內賣出大量

的票的時候,如果沒有同步機制來管理票的數量,將會導致票的總數和售出票數對應不上的錯誤。

我們在售票的過程中給票加上同步鎖:同一時間內,只有一個執行緒能對票的數量進行操作,當操作完成之後,其他執行緒

才能繼續對票的數量進行操作。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - (void)saleTicket { while (1) { @synchronized(self) { //如果還有票,繼續售賣 if (_ticketCount > 0) { _ticketCount --; NSLog(@"%@", [NSString stringWithFormat:@"剩餘票數:%ld 視窗:%@", _ticketCount, [NSThread currentThread].name]); [NSThread sleepForTimeInterval:0.2]; } //如果已賣完,關閉售票視窗 else { break; } } } }

執行結果:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 2016-04-06 19:31:27.913 MutiThread[4718:1406865] 剩餘票數:11 視窗:北京售票視窗 2016-04-06 19:31:28.115 MutiThread[4718:1406866] 剩餘票數:10 視窗:廣州售票視窗 2016-04-06 19:31:28.317 MutiThread[4718:1406865] 剩餘票數:9 視窗:北京售票視窗 2016-04-06 19:31:28.522 MutiThread[4718:1406866] 剩餘票數:8 視窗:廣州售票視窗 2016-04-06 19:31:28.728 MutiThread[4718:1406865] 剩餘票數:7 視窗:北京售票視窗 2016-04-06 19:31:28.929 MutiThread[4718:1406866] 剩餘票數:6 視窗:廣州售票視窗 2016-04-06 19:31:29.134 MutiThread[4718:1406865] 剩餘票數:5 視窗:北京售票視窗 2016-04-06 19:31:29.339 MutiThread[4718:1406866] 剩餘票數:4 視窗:廣州售票視窗 2016-04-06 19:31:29.545 MutiThread[4718:1406865] 剩餘票數:3 視窗:北京售票視窗 2016-04-06 19:31:29.751 MutiThread[4718:1406866] 剩餘票數:2 視窗:廣州售票視窗 2016-04-06 19:31:29.952 MutiThread[4718:1406865] 剩餘票數:1 視窗:北京售票視窗 2016-04-06 19:31:30.158 MutiThread[4718:1406866] 剩餘票數:0 視窗:廣州售票視窗 2016-04-06 19:31:30.363 MutiThread[4718:1406866] <nsthread: 0x7ff0c1637320>
{number = 3, name = 廣州售票視窗} Will Exit
2016-04-06 19:31:30.363 MutiThread[4718:1406865] <nsthread: 0x7ff0c1420cb0>
{number = 2, name = 北京售票視窗} Will Exit</nsthread: 0x7ff0c1420cb0>
</nsthread: 0x7ff0c1637320>

可以看到,票的數量沒有出現錯亂的情況。

執行緒的持續執行和退出

我們注意到,執行緒啟動後,執行saleTicket完畢後就馬上退出了,怎樣能讓執行緒一直執行呢(視窗一直開放,

可以隨時指派其賣演唱會的門票的任務),答案就是給執行緒加上runLoop

1 2 //先監聽執行緒退出的通知,以便知道執行緒什麼時候退出 [[NSNotificationCenter defaultCenter]addObserver:self
selector:@selector(threadExitNotice)
name:NSThreadWillExitNotification object:nil];
1 2 //設定演唱會的門票數量 _ticketCount = 50;

新建兩個子執行緒(代表兩個視窗同時銷售門票)

1 2 3 4 NSThread * window1 = [[NSThread alloc]initWithTarget:self
selector:@selector(thread1) object:nil];
[window1 start]; NSThread * window2 = [[NSThread alloc]initWithTarget:self
selector:@selector(thread2) object:nil];
[window2 start];

接著我們給執行緒建立一個runLoop

1 2 3 4 5 6 7 8 9 10 - (void)thread1 { [NSThread currentThread].name = @"北京售票視窗"; NSRunLoop * runLoop1 = [NSRunLoop currentRunLoop]; [runLoop1 runUntilDate:[NSDate date]]; //一直執行 } - (void)thread2 { [NSThread currentThread].name = @"廣州售票視窗"; NSRunLoop * runLoop2 = [NSRunLoop currentRunLoop]; [runLoop2 runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10.0]]; //自定義執行時間 }

然後就可以指派任務給執行緒了,這裡我們讓兩個執行緒都執行相同的任務(售票)

1 2 [self performSelector:@selector(saleTicket) onThread:window1
withObject:nil waitUntilDone:NO];
[self performSelector:@selector(saleTicket) onThread:window2
withObject:nil waitUntilDone:NO];

執行結果:

1 2 3 4 5 6 7 8 9 10 11 12 13 2016-04-06 19:43:22.585 MutiThread[4762:1478200] 剩餘票數:11 視窗:北京售票視窗 2016-04-06 19:43:22.788 MutiThread[4762:1478201] 剩餘票數:10 視窗:廣州售票視窗 2016-04-06 19:43:22.993 MutiThread[4762:1478200] 剩餘票數:9 視窗:北京售票視窗 2016-04-06 19:43:23.198 MutiThread[4762:1478201] 剩餘票數:8 視窗:廣州售票視窗 2016-04-06 19:43:23.404 MutiThread[4762:1478200] 剩餘票數:7 視窗:北京售票視窗 2016-04-06 19:43:23.609 MutiThread[4762:1478201] 剩餘票數:6 視窗:廣州售票視窗 2016-04-06 19:43:23.810 MutiThread[4762:1478200] 剩餘票數:5 視窗:北京售票視窗 2016-04-06 19:43:24.011 MutiThread[4762:1478201] 剩餘票數:4 視窗:廣州售票視窗 2016-04-06 19:43:24.216 MutiThread[4762:1478200] 剩餘票數:3 視窗:北京售票視窗 2016-04-06 19:43:24.422 MutiThread[4762:1478201] 剩餘票數:2 視窗:廣州售票視窗 2016-04-06 19:43:24.628 MutiThread[4762:1478200] 剩餘票數:1 視窗:北京售票視窗 2016-04-06 19:43:24.833 MutiThread[4762:1478201] 剩餘票數:0 視窗:廣州售票視窗 2016-04-06 19:43:25.039 MutiThread[4762:1478201]
<nsthread: 0x7fe0d3c24360>{number = 3, name = 廣州售票視窗}
Will Exit</nsthread: 0x7fe0d3c24360>

可以看到,當票賣完後,兩個執行緒並沒有退出,仍在繼續執行,當到達指定時間後,執行緒2退出了,

如果需要讓執行緒1退出,需要我們手動管理。

比如我們讓執行緒完成任務(售票)後自行退出,可以這樣操作

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 - (void)saleTicket { while (1) { @synchronized(self) { //如果還有票,繼續售賣 if (_ticketCount > 0) { _ticketCount --; NSLog(@"%@", [NSString stringWithFormat:@"剩餘票數:%ld 視窗:%@", _ticketCount, [NSThread currentThread].name]); [NSThread sleepForTimeInterval:0.2]; } //如果已賣完,關閉售票視窗 else { if ([NSThread currentThread].isCancelled) { break; }else { NSLog(@"售賣完畢"); //給當前執行緒標記為取消狀態 [[NSThread currentThread] cancel]; //停止當前執行緒的runLoop CFRunLoopStop(CFRunLoopGetCurrent()); } } } } }

執行結果:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 2016-04-06 20:08:38.287 MutiThread[4927:1577193] 剩餘票數:10 視窗:北京售票視窗 2016-04-06 20:08:38.489 MutiThread[4927:1577194] 剩餘票數:9 視窗:廣州售票視窗 2016-04-06 20:08:38.690 MutiThread[4927:1577193] 剩餘票數:8 視窗:北京售票視窗 2016-04-06 20:08:38.892 MutiThread[4927:1577194] 剩餘票數:7 視窗:廣州售票視窗 2016-04-06 20:08:39.094 MutiThread[4927:1577193] 剩餘票數:6 視窗:北京售票視窗 2016-04-06 20:08:39.294 MutiThread[4927:1577194] 剩餘票數:5 視窗:廣州售票視窗 2016-04-06 20:08:39.499 MutiThread[4927:1577193] 剩餘票數:4 視窗:北京售票視窗 2016-04-06 20:08:39.700 MutiThread[4927:1577194] 剩餘票數:3 視窗:廣州售票視窗 2016-04-06 20:08:39.905 MutiThread[4927:1577193] 剩餘票數:2 視窗:北京售票視窗 2016-04-06 20:08:40.106 MutiThread[4927:1577194] 剩餘票數:1 視窗:廣州售票視窗 2016-04-06 20:08:40.312 MutiThread[4927:1577193] 剩餘票數:0 視窗:北京售票視窗 2016-04-06 20:08:40.516 MutiThread[4927:1577194] 售賣完畢 2016-04-06 20:08:40.516 MutiThread[4927:1577193] 售賣完畢 2016-04-06 20:08:40.517 MutiThread[4927:1577193]
<nsthread: 0x7fb719d54000>{number = 2, name = 北京售票視窗} Will Exit
2016-04-06 20:08:40.517 MutiThread[4927:1577194]
<nsthread: 0x7fb719d552f0>{number = 3, name = 廣州售票視窗}
Will Exit</nsthread: 0x7fb719d552f0></nsthread: 0x7fb719d54000>

如果確定兩個執行緒都是isCancelled狀態,可以呼叫[NSThread exit]方法來終止執行緒。

NSThread

  • 一個NSThread物件就代表一條執行緒
  • NSThread會在執行完任務函式是被自動收回
  • 一些常用的函式
    //建立執行緒
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector() object:nil];
    //object存的是引數

    //修改引數名
    thread.name = @"我的多執行緒";

    //獲取主執行緒
    NSThread *thread = [NSThread mainThread];

    //建立執行緒並自動啟動
    [NSThread detachNewThreadSelector:@selector() toTarget:self withObject:nil];

    //隱士建立
    [self performSelectorInBackground:@selector() withObject:nil];

    //獲取當前執行緒
    [NSThread currentThread]

    //啟動執行緒
    - (void)start;


    //阻塞(暫停)執行緒
    + (void)sleepUntilDate:(NSDate *)date;
    + (void)sleepForTimeInterval:(NSTimeInterval)ti;
    // 進入阻塞狀態

    //停止當前正在執行的程序
    + (void)exit;
    // 進入死亡狀態,一旦死亡則不能重啟

資源共享

  • 1塊資源可能會被多個執行緒共享,也就是多個執行緒可能會訪問同一塊資源
  • 當多個執行緒訪問同一塊資源時,很容易引發資料錯亂和資料安全問題

解決辦法互斥鎖

  • 互斥鎖使用格式

    • @synchronized(鎖物件) { // 需要鎖定的程式碼 }
    • 只用一把鎖,多鎖是無效的
  • 互斥鎖的優缺點

    • 優點:能有效防止因多執行緒搶奪資源造成的資料安全問題
    • 缺點:需要消耗大量的CPU資源
  • 互斥鎖的使用前提:多條執行緒搶奪同一塊資源

  • 互斥鎖的示例程式碼
#import "ViewController.h"

@interface ViewController ()
/** 售票機1*/
@property (strong,nonatomic) NSThread *threadOne;
/** 售票機2*/
@property (strong,nonatomic) NSThread *threadTwo;
/** 售票機3*/
@property (strong,nonatomic) NSThread *threadThree;
/** 售票機4*/
@property (strong,nonatomic) NSThread *threadFour;
/** 售票機5*/
@property (strong,nonatomic) NSThread *threadFive;

/** 數量*/
@property (assign,nonatomic) NSInteger count;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //建立執行緒
    self.threadOne = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"one"];
    self.threadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"two"];
    self.threadThree = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"three"];
    self.threadFour = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"four"];
    self.threadFive = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"five"];
    self.count = 100;
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //開始執行緒
    [self.threadOne start];
    [self.threadTwo start];
    [self.threadThree start];
    [self.threadFour start];
    [self.threadFive start];
}


- (void)run:(NSString *)param
{
    while (1) {
        //self是鎖物件
        @synchronized(self)
        {

            if (self.count > 0) {
                _count--;
                NSLog(@"%zd-----%@",self.count,[NSThread currentThread]);
            }
            else
            {
                NSLog(@"賣完了----%@",[NSThread currentThread]);
                break;
            }
        }
    }
}
@end