天天看點

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]方法來終止線程。