前面已經(jīng)有一篇文章(學(xué)習(xí)GCD看我就夠了)專門介紹了GCD,下面來介紹一下另外三個與多線程相關(guān)的方法
一、pthreads(現(xiàn)在幾乎不用了)
pthread是POSIX thread的簡寫,一套通用的多線程API,適用于Unix、Linux、Windows等系統(tǒng),跨平臺、可移植,使用難度大,C語言框架,線程生命周期由程序員管理,由于iOS開發(fā)幾乎用不到,以下就簡單運(yùn)用pthread開啟一個子線程,用來處理耗時操作
// 創(chuàng)建線程,并且在線程中執(zhí)行 demo 函數(shù)
- (void)pthreadDemo {
返回值:
- 若線程創(chuàng)建成功,則返回0
- 若線程創(chuàng)建失敗,則返回出錯編號
*/
pthread_t threadId = NULL;
NSString *str = @"Hello Pthread";
// 這邊的demo函數(shù)名作為第三個參數(shù)寫在這里可以在其前面加一個&,也可以不加,因?yàn)楹瘮?shù)名就代表了函數(shù)的地址。
int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(str));
if (result == 0) {
NSLog(@"創(chuàng)建線程 OK");
} else {
NSLog(@"創(chuàng)建線程失敗 %d", result);
}
// pthread_detach:設(shè)置子線程的狀態(tài)設(shè)置為detached,則該線程運(yùn)行結(jié)束后會自動釋放所有資源。
pthread_detach(threadId);
}
// 后臺線程調(diào)用函數(shù)
void *demo(void *params) {
NSString *str = (__bridge NSString *)(params);
NSLog(@"%@ - %@", [NSThread currentThread], str);
return NULL;
}
二、NSThread
NSThread是基于線程使用,輕量級的多線程編程方法(相對GCD和NSOperation),一個NSThread對象代表一個線程,需要手動管理線程的生命周期,處理線程同步等問題。
創(chuàng)建線程
- 方法一
// 1. 創(chuàng)建線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test:) object:nil];
thread.name = @"thread1"; //設(shè)置線程名
[thread start]; // 2. 啟動線程,此方法需要我們手動開啟線程
- (void)test:(NSString *)string {
NSLog(@"test - %@ - %@", [NSThread currentThread], string);
}
打印如下:
2017-09-29 10:28:44.203914+0800 aegewgr[9577:3142906] test - <NSThread: 0x600000461a40>{number = 3, name = thread1} - (null)
這里我們最好設(shè)置一下線程名,便于我們的調(diào)試
- 方法二
[NSThread detachNewThreadSelector:@selector(test:) toTarget:self withObject:@"分離子線程"];
該方法會自動創(chuàng)建一個子線程,并在子線程中執(zhí)行
2017-09-29 10:33:21.702512+0800 aegewgr[9617:3159015] test - <NSThread: 0x6000004621c0>{number = 4, name = (null)} - 分離子線程
- 方法三
[self performSelectorInBackground:@selector(test:) withObject:@"后臺線程"];
該方法會開啟一條后臺線程,并在后臺線程中執(zhí)行。
上面所有的方法都還有與之對應(yīng)的通過block創(chuàng)建的方法。
另外通過線程間通信的幾個方法
[self performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>]
[self performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#> modes:<#(nullable NSArray<NSString *> *)#>]
[self performSelector:<#(nonnull SEL)#> onThread:<#(nonnull NSThread *)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>]
[self performSelector:<#(nonnull SEL)#> onThread:<#(nonnull NSThread *)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#> modes:<#(nullable NSArray<NSString *> *)#>]
關(guān)于進(jìn)程間通信,可以看我另一篇文章Runloop的應(yīng)用與深入理解
還有一下常用的屬性和方法直接去看文檔就可以了。
三、NSOperation和NSOperationQueue
NSOperation是基于GCD開發(fā)的,但是比GCD擁有更強(qiáng)的可控性和代碼可讀性。NSOperation是一個抽象基類,表示一個獨(dú)立的計(jì)算單元,可以為子類提供有用且線程安全的建立狀態(tài),優(yōu)先級,依賴和取消等操作。我們使用比較多的就是它的子類NSInvocationOperation和NSBlockOperation。不過我們更多的使用是自己繼承并定制自己的操作。
使用NSInvocationOperation
NSInvocationOperation *invo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test:) object:nil];
[invo start];
NSLog(@"111");
- (void)test:(NSString *)string {
sleep(1);
NSLog(@"test - %@ - %@", [NSThread currentThread], string);
}
2017-09-29 14:19:13.242517+0800 aegewgr[10143:3388734] test - <NSThread: 0x600000078180>{number = 1, name = main} - (null)
2017-09-29 14:19:13.242967+0800 aegewgr[10143:3388734] 111
可以看到NSInvocationOperation是同步并且串行的,所以只是用NSInvocationOperation并沒有什么卵用,主要還是要和NSOperationQueue結(jié)合使用。這個放到后面再講。
使用NSBlockOperation
NSBlockOperation支持并發(fā)的實(shí)行一個或多個block,使用起來非常方便
NSBlockOperation *blockOperation = [[NSBlockOperation alloc]init];
[blockOperation addExecutionBlock:^{
NSLog(@"block 1 in thread:%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"block 2 in thread:%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"block 3 in thread:%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"block 4 in thread:%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
sleep(1);
NSLog(@"block 5 in thread:%@",[NSThread currentThread]);
}];
[blockOperation start];
NSLog(@"123");
2017-09-29 14:32:03.710936+0800 aegewgr[10335:3439694] block 1 in thread:<NSThread: 0x60400006d740>{number = 1, name = main}
2017-09-29 14:32:03.710939+0800 aegewgr[10335:3439916] block 3 in thread:<NSThread: 0x60400027a780>{number = 4, name = (null)}
2017-09-29 14:32:03.710943+0800 aegewgr[10335:3439920] block 4 in thread:<NSThread: 0x60400027a840>{number = 5, name = (null)}
2017-09-29 14:32:03.710961+0800 aegewgr[10335:3439919] block 2 in thread:<NSThread: 0x600000270c00>{number = 3, name = (null)}
2017-09-29 14:32:04.712532+0800 aegewgr[10335:3439920] block 5 in thread:<NSThread: 0x60400027a840>{number = 5, name = (null)}
2017-09-29 14:32:04.712932+0800 aegewgr[10335:3439694] 123
注意,這里我故意讓block5 sleep了1秒才執(zhí)行。而123這個輸出也是直到block5執(zhí)行完了才執(zhí)行,所以,NSBlockOperation也是同步的,而block的執(zhí)行是并發(fā)的。至于串行隊(duì)列并發(fā)隊(duì)列與同步異步的概念可以參考我前面提到的那篇文章。
自定義NSOperation
自定義NSOperation分兩種,一種是自定義非并發(fā)的NSOperation,一種是定義并發(fā)的NSOperation的。下面分別介紹。
- 定義非并發(fā)的NSOperation
如果是自定義非并發(fā)的NSOperation,只需要重寫main方法就夠了。
#import "SerialNSOperation.h"
@implementation SerialNSOperation
- (void)main
{
NSLog(@"main begin");
@try {
//在這里我們要創(chuàng)建自己的釋放池,因?yàn)檫@里我們拿不到主線程的釋放池
@autoreleasepool {
// 提供一個變量標(biāo)識,來表示需要執(zhí)行的操作是否完成了,當(dāng)然,沒開始執(zhí)行之前,為NO
BOOL taskIsFinished = NO;
// while 保證:只有當(dāng)沒有執(zhí)行完成和沒有被取消,才執(zhí)行自定義的相應(yīng)操作
while (taskIsFinished == NO && [self isCancelled] == NO){
// 自定義的操作
NSLog(@"currentThread = %@", [NSThread currentThread]);
sleep(10); // 模擬耗時操作
// 這里相應(yīng)的操作都已經(jīng)完成,后面就是要通知KVO我們的操作完成了。
taskIsFinished = YES;
}
}
}
@catch (NSException * e) {
NSLog(@"Exception %@", e);
}
NSLog(@"main end");
}
然后直接使用
SerialNSOperation *op = [[SerialNSOperation alloc]init];
[op start];
2017-09-29 15:12:59.151481+0800 aegewgr[10524:3564080] main begin
2017-09-29 15:13:09.152082+0800 aegewgr[10524:3564080] currentThread = <NSThread: 0x60400006e1c0>{number = 1, name = main}
2017-09-29 15:13:09.152299+0800 aegewgr[10524:3564080] main end
其實(shí)我感覺這個實(shí)用性不大。
- 定義并發(fā)的NSOperation
自定義并發(fā)的NSOperation需要以下步驟:
1.start方法:該方法必須實(shí)現(xiàn),
2.main:該方法可選,如果你在start方法中定義了你的任務(wù),則這個方法就可以不實(shí)現(xiàn),但通常為了代碼邏輯清晰,通常會在該方法中定義自己的任務(wù)
3.isExecuting isFinished 主要作用是在線程狀態(tài)改變時,產(chǎn)生適當(dāng)?shù)腒VO通知
4.isAsynchronous :必須覆蓋并返回YES;
//.h
#import <Foundation/Foundation.h>
@interface ConcurrentOperation : NSOperation{
BOOL executing;
BOOL finished;
}
//.m
#import "ConcurrentOperation.h"
@implementation ConcurrentOperation
- (id)init {
if(self = [super init])
{
executing = NO;
finished = NO;
}
return self;
}
- (BOOL)isAsynchronous {
return YES;
}
- (BOOL)isExecuting {
return executing;
}
- (BOOL)isFinished {
return finished;
}
- (void)start {
//第一步就要檢測是否被取消了,如果取消了,要實(shí)現(xiàn)相應(yīng)的KVO
if ([self isCancelled]) {
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
//如果沒被取消,開始執(zhí)行任務(wù)
[self willChangeValueForKey:@"isExecuting"];
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
executing = YES;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)main {
NSLog(@"main begin");
@try {
@autoreleasepool {
//在這里定義自己的并發(fā)任務(wù)
NSLog(@"自定義并發(fā)操作NSOperation");
NSThread *thread = [NSThread currentThread];
NSLog(@"current Thread:%@",thread);
//任務(wù)執(zhí)行完成后要實(shí)現(xiàn)相應(yīng)的KVO
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
}
@catch (NSException * e) {
NSLog(@"Exception %@", e);
}
NSLog(@"main end");
}
//調(diào)用
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
ConcurrentOperation *op1 = [[ConcurrentOperation alloc]init];
ConcurrentOperation *op2 = [[ConcurrentOperation alloc]init];
ConcurrentOperation *op3 = [[ConcurrentOperation alloc]init];
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
打印如下:
2017-09-29 15:34:15.158649+0800 aegewgr[10664:3638228] main begin
2017-09-29 15:34:15.158653+0800 aegewgr[10664:3638226] main begin
2017-09-29 15:34:15.158675+0800 aegewgr[10664:3638227] main begin
2017-09-29 15:34:15.158912+0800 aegewgr[10664:3638228] 自定義并發(fā)操作NSOperation
2017-09-29 15:34:15.159321+0800 aegewgr[10664:3638226] 自定義并發(fā)操作NSOperation
2017-09-29 15:34:15.159372+0800 aegewgr[10664:3638227] 自定義并發(fā)操作NSOperation
2017-09-29 15:34:15.159965+0800 aegewgr[10664:3638226] current Thread:<NSThread: 0x60400046b640>{number = 4, name = (null)}
2017-09-29 15:34:15.160014+0800 aegewgr[10664:3638228] current Thread:<NSThread: 0x60000026d140>{number = 5, name = (null)}
2017-09-29 15:34:15.160103+0800 aegewgr[10664:3638227] current Thread:<NSThread: 0x60400046b5c0>{number = 3, name = (null)}
2017-09-29 15:34:15.160799+0800 aegewgr[10664:3638226] main end
2017-09-29 15:34:15.160973+0800 aegewgr[10664:3638227] main end
2017-09-29 15:34:15.161154+0800 aegewgr[10664:3638228] main end
為了展示并發(fā)執(zhí)行,所以我這里使用了NSOperationQueue,后面我在繼續(xù)講這個。
使用NSOperationQueue
NSOperationQueue就是執(zhí)行NSOperation的隊(duì)列,我們可以將一個或多個NSOperation對象放到隊(duì)列中去執(zhí)行。NSOperationQueue有兩種不同類型的隊(duì)列:主隊(duì)列和自定義隊(duì)列。主隊(duì)列運(yùn)行在主線程之上,而自定義隊(duì)列在后臺執(zhí)行。
使用起來很簡單:
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; //主隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //自定義隊(duì)列
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
//任務(wù)執(zhí)行
}];
[queue addOperation:operation];
我們可以通過設(shè)置 maxConcurrentOperationCount 屬性來控制并發(fā)任務(wù)的數(shù)量,當(dāng)設(shè)置為 1時, 那么它就是一個串行隊(duì)列。主對列默認(rèn)是串行隊(duì)列,這一點(diǎn)和 dispatch_queue_t是相似的。前面也說過,NSOperation就是基于GCD開發(fā)的。
NSOperationQueue相對于GCD來說有以下優(yōu)點(diǎn):
- 提供了在 GCD 中不那么容易復(fù)制的有用特性。
- 可以很方便的取消一個NSOperation的執(zhí)行
- 可以更容易的添加任務(wù)的依賴關(guān)系
- 提供了任務(wù)的狀態(tài):isExecuteing, isFinished.
以上就是多線程相關(guān)的所有方法了,具體使用什么方法還是看你的需求。如果我講的有什么錯誤的地方希望大家指正。