Skip to content

Latest commit

 

History

History
387 lines (337 loc) · 13.3 KB

Thread_04.md

File metadata and controls

387 lines (337 loc) · 13.3 KB

iOS多线程使用方法记录

本文是多年使用多线程开发的心得,偏基础。

pthread&NSThread

pthread

pthread,即POSIX Thread,是一套可以跨平台通用的多线程API,基于C语言。 它可以在许多类似Unix且符合POSIX的操作系统上可用,例如FreeBSD,NetBSD,OpenBSD,Linux,iOS/macOS,Android。如果要实现一个跨平台的库,使用它实际上是个很不错的选择,不过单就iOS平台而言,并不推荐使用,而且我也确实没有使用过,就不做过多的介绍。在阅读源码发现使用pthread的时候,现查就可以了。

NSThread

NSThread是苹果官方提供的一个操作线程的API,比pthread更加简单使用,可以直接操作线程对象,但是同样的也需要我们手动的管理线程的生命周期。 因为往往手动处理线程的生命周期往往会带来很多麻烦,所以苹果官方强烈建议我们不要手动的终止线程;即使非要用,也要在一开始就设计好线程以响应取消或退出消息。 官方建议我们使用initWithTarget:selector:object:方法,然后手动启动。

NSThread* myThread = [[NSThread alloc] initWithTarget:self   
                                selector:@selector(myThreadMainMethod:)
                                        object:nil];
[myThread start];  // Actually create the thread
......
[myThread exit];  // Terminates the current thread.

我使用NSThread的地方也很少,一般仅限以下的几种方法:

  • 获取当前线程 NSThread *current = [NSThread currentThread];
  • 是否是多线程 BOOL isMainThread = [NSThread isMultiThreaded];

对,仅此而已了。 原本NSThread还有两处地方可以使用--线程保活和判断是否是主线程。 但是这个两个都存在一些问题,已经不建议使用。 在AFNetWorking的2.x版本里,有这样一段线程保活的代码。

        [[NSThread currentThread] setName:@"AFNetworking"];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];

但是这仅仅只是权宜之计,在后来的版本里已经替换为GCD的API了,这是为什么呢? 第一,NSThread本身已经很老的,很多设计已经过时,它会持续的占用新开辟线程的资源;第二,使用NSThread创建的长寿线程,并不能持续的满载的运行,这个实际上也是一种资源的莱菲,而GCD通过内核调度,动态的分配资源,相对的节约了资源。 在SDWebImage的老版本里,因为UI操作一定要在主线程上运行,所以会在很多地方判断当前手否是主线程。当时使用的是下面这个API。

#define dispatch_main_sync_safe(block)\
    if ([NSThread isMainThread]) {\
        block();\
    } else {\
        dispatch_sync(dispatch_get_main_queue(), block);\
    }

但是如果对SDWebImage源码比较熟悉的话,我们会知道在大约4.0之后的版本里,这个API被替换成了下面。

#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
    if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(dispatch_get_main_queue())) {\
        block();\
    } else {\
        dispatch_async(dispatch_get_main_queue(), block);\
    }
#endif

这个原因,可以查看这个issue。简单而言,就是因为在MapKit中有一个坑,导致有问题了,所以现在基本上都弃用原有的方法。

GCD

GCD就不必过多介绍了,我们一般使用的最多的异步API。不必手动管理创建的线程,只需要讲想要异步使用的代码放到适当的Dispatch Queue当中,GCD就会生成必要的线程来执行任务。 GCD实际上是由内核来直接调度资源分配,所以会比上面的更加有效率和节约。 下面简单描述一下用的比较多的功能。

多线程应用

在其他线程运行程序有两种方法。 第一种。

    dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        sleep(1);
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"mainQueue");
        });
    });
    

第二种方法。

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(5);
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"mainQueue");
        });
    });

延后执行

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"wait 5 sec");
    });

这里有些地方需要注意。 dispatch_after方法实际上并不是在指定时间后执行处理,而是在指定的时间处加入到DispatchQueue中。 因为主队列在主线程中RunLoop中执行,RunLoop有时间间隔的,block中的方法最快是在5后执行,最慢则是在 5+1/60 秒后执行。

队列

假如有多个任务要一起处理,就可以使用这个方法。

+ (void)dispatchGroup {
    dispatch_queue_t queue = dispatch_queue_create("queue_test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"任务1");
        sleep(3);
        NSLog(@"任务2");
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"任务完成");
        NSLog(@"%d",[NSThread isMainThread]);
    });
}

快速遍历

dispatch_appy是GCD提供的一个快速遍历的方法。但是要注意,这个遍历的方法是无序的,并且最好自己去新建一个异步任务。

    dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(1000, queue, ^(size_t index) {
        NSLog(@"apply is %zu",index);
    });

dispatch_apply的优点在于,它比forin遍历还要快速!!! 如果看过我之前的[文章]的话,应该知道forin遍历在大多数遍历里算得上最快的了,而dispatch_apply比forin遍历还要快速。在某些数量很大并且不需要顺序的遍历操作中可以使用。

Semaphore

这个实际上是一个信号量。我们可以把它理解为一种锁。 Semaphore是一个有计数的信号。计数为0的时候回阻塞线程,大于0则不会,所以我们可以通过控制semaphore的值,来达成线程同步。

这里主要是三个函数起作用。

  • dispatch_semaphore_create创建一个semaphore信号量;
  • dispatch_semaphore_signal 发送一个信号让信号量+1;
  • dispatch_semaphore_wait 如果信号量计数为0则阻塞等待、否则通过。
        dispatch_group_t group = dispatch_group_create();
    // 创建信号量,并且设置值为10
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        for (int i = 0; i < 100; i++){
            //信号-1
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            dispatch_group_async(group, queue, ^{
                NSLog(@"%i",i);
                sleep(2);
                //信号+1,
                dispatch_semaphore_signal(semaphore);
            });
        }

dispatch_semaphore有个优点是性能比较好,如果看过SDWebImage的源码的话,会发现它的很多锁是从@synchronized慢慢的替换为dispatch_semaphore。

单例

这个可能是我们使用的最多的地方了。 比较安全的单例写法是这样的。

#ifdef __has_feature(objc_arc) 

#define singleton_h +(instancetype)sharedInstance;

#define singleton_m static id _instanceType = nil;\
+(instancetype)sharedInstance\
{\
    static dispatch_once_t onceToken;\
    dispatch_once(&onceToken, ^{\
        _instanceType = [[self alloc]init];\
    });\
    return _instanceType;\
}\
+ (instancetype)allocWithZone:(struct _NSZone *)zone\
{\
    static dispatch_once_t onceToken;\
    dispatch_once(&onceToken, ^{\
        _instanceType = [super allocWithZone:zone];\
    });\
    return _instanceType;\
}\
-(id)copyWithZone:(NSZone *)zone\
{\
    return _instanceType;\
}

#else 

#define singleton_h +(instancetype)sharedInstance;

#define singleton_m static id _instanceType = nil;\
+(instancetype)sharedInstance\
{\
    static dispatch_once_t onceToken;\
    dispatch_once(&onceToken, ^{\
        _instanceType = [[self alloc]init];\
    });\
    return _instanceType;\
}\
+ (instancetype)allocWithZone:(struct _NSZone *)zone\
{\
    static dispatch_once_t onceToken;\
    dispatch_once(&onceToken, ^{\
        _instanceType = [super allocWithZone:zone];\
    });\
    return _instanceType;\
}\
-(id)copyWithZone:(NSZone *)zone\
{\
    return _instanceType;\
}\
-(oneway void)release\
{\
\
}\
-(instancetype)retain\
{\
    return _instanceType;\
}\
-(instancetype)autorelease\
{\
    return _instanceType;\
}\
- (NSUInteger)retainCount\
{\
    return 1;\
}

#endif

定时器

直接去看这里

@interface ViewController ()

@property (nonatomic, strong) dispatch_source_t timer;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 创建
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());

    // 开始时刻
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (ino64_t)(1.0 * NSEC_PER_SEC));
    uint64_t interval = (uint64_t)(2.0 * NSEC_PER_SEC);
    dispatch_source_set_timer(self.timer, start, interval, 0);
    dispatch_source_set_event_handler(self.timer, ^{

        NSLog(@"time");

    });

    // 启动
    dispatch_resume(self.timer);

}

//暂停
-(void) pauseTimer{  
    if(_timer){  
        dispatch_suspend(_timer);  
    }  
}  
//恢复
-(void) resumeTimer{  
    if(_timer){  
        dispatch_resume(_timer);  
    }  
}  
//销毁
-(void) stopTimer{  
    if(_timer){  
        dispatch_source_cancel(_timer);  
        _timer = nil;  
    }  
}  

@end

NSOperation

比较惭愧的说,我其实使用NSOperation的并不多。

NSOperation是对GCD的封装,更加的面相对象。

它主要有三种对象。

  • NSInvocationOperation
  • NSBlockOperation
  • 自定义NSOperation

NSInvocationOperation

+ (void)InvocationOperation {
    
    // 1.创建 NSInvocationOperation 对象
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];

    // 2.调用 start 方法开始执行操作
    [op start];
}


- (void)task1 {
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
        NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
    }
}

我们可以发现,队列实际上还是在主线程中。

NSBlockOperation

    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [op start];

我们可以发现,队列实际上还是在主线程中。

自定义NSOperation

通过重写NSOperation的方法来处理,代码略过。

addOperationWithBlock

我们使用这个方法来添加异步任务。

- (void)addOperationWithBlockToQueue {
    // 1.创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.使用 addOperationWithBlock: 添加操作到队列中
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
}

我们可以发现,使用 addOperationWithBlock: 将操作加入到操作队列后能够开启新线程,进行并发执行。

操作依赖

我在少数使用NSOperation的地方就是这里了。 有时候我们会遇到a任务、b任务要先后执行,然后在执行完毕之后才能继续执行c操作。这个时候NSOperation就有用了。 示例代码如下:

+ (void)addDependency {
    //创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //创建操作
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
        }
    }];
    //添加依赖
    [op2 addDependency:op1]; 
    //添加操作到队列中
    [queue addOperation:op1];
    [queue addOperation:op2];
}