1. 程式人生 > >iOS開發之多執行緒NSThread

iOS開發之多執行緒NSThread

之前的文章中介紹,多執行緒能夠提高應用程式的執行效率,把耗時的操作放在子執行緒中執行,這樣不會阻塞主執行緒的執行,不會給使用者“卡”的體驗。本文主要介紹多執行緒程式設計的第一種方式。

由於pthread是底層的多執行緒程式設計API,對於pthread,瞭解如何讓建立子執行緒即可。使用pthread,需要匯入標頭檔案#import<pthread.h>

建立子執行緒如下:

void *run(void *data) {
    
    NSThread *current = [NSThread currentThread];
    
    for (int i = 0; i<20000; i++) {
        NSLog(@"run---%@", current);
    }
    
    return NULL;
}

- (IBAction)btnClick {
    // 1.獲得當前的執行緒
    NSThread *current = [NSThread currentThread];
    NSLog(@"btnClick---%@", current);
    
    // 2.執行一些耗時操作 : 建立一條子執行緒
    pthread_t threadId;
    pthread_create(&threadId, NULL, run, NULL);
}

NSThread

優點:NSThread GCDNSOperation兩個輕量級

缺點:需要自己管理執行緒的生命週期,執行緒同步。執行緒同步對資料的加鎖會有一定的系統開銷

建立和啟動執行緒

一個NSThread物件就代表一條執行緒。

首先來了解關於NSThread的一些方法:

主執行緒相關用法

+ (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;

1.    建立、啟動執行緒

NSThread *thread= [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];// 執行緒一啟動,就會線上程thread中執行self的run方法

2.    建立執行緒後自動啟動執行緒

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

3.    隱式建立並自動啟動執行緒

[self performSelectorInBackground:@selector(run) withObject:nil];

建立執行緒方式2、3優點:簡單快捷;缺點:無法對執行緒進行更詳細的設定。

執行緒狀態

知道如何建立執行緒,那麼我們來看看,執行緒從建立到死亡(銷燬)都經歷了哪些狀態。

我們利用如下程式碼來講解:

NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];

[thread start];

1>當執行緒被建立開始,執行緒就進入了新建狀態

2>當呼叫start方法後,執行緒有可能沒有被CPU排程則執行緒進入了就緒狀態,如果呼叫start方法後,執行緒直接被CPU排程則將執行緒就進入了執行狀態

3>當CPU排程了其他執行緒,則當前執行緒就進入就緒狀態

4>當呼叫了sleep方法\等待同步鎖的時候,執行緒暫停執行,則執行緒進入了阻塞狀態

5>當阻塞狀態下得執行緒sleep到時\得到同步鎖後,執行緒又進入了就緒狀態

6>當執行緒任務完成\異常退出\強制退出,則執行緒就進入死亡狀態,執行緒就被銷燬。

如下圖:

注意:執行緒建立後,會被放在程序中可排程執行緒池中,可排程執行緒池中的執行緒在CPU的排程下執行。當執行緒被阻塞\死亡,則執行緒會從可排程執行緒池中移出\銷燬。

控制器執行緒狀態的方法

啟動執行緒

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

阻塞(暫停)執行緒

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

強制停止執行緒

+ (void)exit;// 進入死亡狀態

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

多執行緒雖然能提高應用程式的執行效率,同時多執行緒也存在一定的安全隱患。

1.    資源共享

一塊資源可能會被多個執行緒共享,也就是多個執行緒可能會訪問同一塊資源。比如多個執行緒訪問同一個物件、同一個變數、同一個檔案

2.    當多個執行緒訪問同一塊資源時,很容易引發資料錯亂和資料安全問題

如下圖:

講解:有一個整形變數number = 17,執行緒A要訪問變數number,並獲得它的值為17,執行+1操作;這個時候執行緒B也訪問了變數number,並獲得它的值(此時執行緒A還沒執行寫入操作),則值為17,執行了+1操作,最後才執行寫入操作。執行緒A寫入完得18,執行緒B寫入完也是18。這樣就引發了資料安全的問題。

在iOS開發中,多執行緒是非常重要的技術,那麼如何避免多執行緒帶來的安全隱患呢?

在多執行緒訪問同一資源的情況下,可以將資源進行加鎖(互斥鎖\同步鎖)。

如下圖:

講解:有一個整形變數number = 17,執行緒A讀取變數number之後,變數number被加上鎖,再執行操作;這個時候執行緒B也要訪問變數number,因為被加上了鎖,所以執行緒B無法訪問。等執行緒A對變數number執行完操作後,number會開啟鎖,這樣執行緒B就可以訪問,訪問的時候也要加鎖,執行完操作後,再把鎖開啟,這樣就可以避免資料不安全的隱患了。

在iOS開發中實現加鎖如下:

互斥鎖使用格式(任何物件都可以當做一把鎖

@synchronized(鎖物件) { //需要鎖定的程式碼}

注意:鎖定1份程式碼只用1把鎖,用多把鎖是無效的

 互斥鎖的優缺點

優點:能有效防止因多執行緒搶奪資源造成的資料安全問題

缺點:需要消耗大量的CPU資源

 互斥鎖的使用前提:多條執行緒搶奪同一塊資源

 相關專業術語:執行緒同步

執行緒同步的意思是:多條執行緒按順序地執行任務

互斥鎖,就是使用了執行緒同步技術

其實在開發中我們經常遇到加鎖的加鎖的情況,只是我們沒有使用。就是原子和非原子屬性

OC在定義屬性時有nonatomic和atomic兩種選擇

atomic:原子屬性,為setter方法加鎖(預設就是atomic)

nonatomic:非原子屬性,不會為setter方法加鎖

 atomic加鎖原理

@property (assign, atomic) int age;
- (void)setAge:(int)age
{
    @synchronized(self){
       _age = age;
    }
}
nonatomic和atomic對比

atomic:執行緒安全,需要消耗大量的資源

nonatomic:非執行緒安全,適合記憶體小的移動裝置

執行緒間通訊

既然多執行緒提高了應用程式的執行效率,但是在多條執行緒之間要想進行資料互動怎麼辦呢?這就用到了執行緒間通訊。

執行緒間通訊:在1個程序中,執行緒往往不是孤立存在的,多個執行緒之間需要經常進行通訊

 執行緒間通訊的體現

1.1個執行緒傳遞資料給另1個執行緒

2.在1個執行緒中執行完特定任務後,轉到另1個執行緒繼續執行任務

 執行緒間通訊常用方法

-(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)argwaitUntilDone:(BOOL)wait;

- (void)performSelector:(SEL)aSelectoronThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
執行緒間通訊如圖:

IOS開發中實現多執行緒技術的第一種方法就介紹到這裡。