iOS多线程总结

多线程 优缺点,实际应用


多线程比较

死锁:使用同步sync,向同一个/当前的串行队添加任务,会产生死锁
新等旧,旧等新

1- NSThread:
–优点:NSThread 比其他两个轻量级,使用简单
–缺点:需要自己管理线程的生命周期、线程同步、加锁、睡眠以及唤醒等。线程同步对数据的加锁会有一定的系统开销

2- NSOperation:
–不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上
–NSOperation是面向对象的
核心概念:把操作(异步)添加到队列(全局的并发队列)
OC 框架,更加面向对象,是对 GCD 的封装
iOS 2.0 推出的,苹果推出 GCD 之后,对 NSOperation 的底层全部重写
Operation作为一个对象,为我们提供了更多的选择
可以随时取消已经设定要准备执行的任务,已经执行的除外
可以跨队列设置操作的依赖关系
可以设置队列中每一个操作的优先级
高级功能:
最大操作并发数(GCD不好做)
继续/暂停/全部取消
跨队列设置操作的依赖关系

3- GCD:
–Grand Central Dispatch是由苹果开发的一个多核编程的解决方案。iOS4.0+才能使用,是替代NSThread, NSOperation的高效和强大的技术
–GCD是基于C语言的
将任务(block)添加到队列(串行/并发/主队列),并且指定任务执行的函数(同步/异步)
GCD是底层的C语言构成的API
iOS 4.0 推出的,针对多核处理器的并发技术
在队列中执行的是由 block 构成的任务,这是一个轻量级的数据结构
要停止已经加入 queue 的 block 需要写复杂的代码
需要通过 Barrier 或者同步任务设置任务之间的依赖关系
只能设置队列的优先级
高级功能:
一次性 once
延迟操作 after
调度组

比较
NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。
GCD主要与block结合使用。代码简洁高效

  1. 性能:GCD更接近底层,而NSOperationQueue则更高级抽象,所以GCD在追求性能的底层操作来说,是速度最快的。这取决于使用Instruments进行代码性能分析,如有必要的话
  2. 从异步操作之间的事务性,顺序行,依赖关系。GCD需要自己写更多的代码来实现,而NSOperationQueue已经内建了这些支持
  3. 如果异步操作的过程需要更多的被交互和UI呈现出来,NSOperationQueue会是一个更好的选择。底层代码中,任务之间不太互相依赖,而需要更高的并发能力,GCD则更有优势

GCD的工作原理是:让程序平行排队的特定任务,根据可用的处理资源,安排在任何可用的处理器核心上执行任务。
一个任务可以是一个函数(function)或者是一个block。 GCD的底层依然是用线程实现,不过这样可以让程序员不用关注实现的细节。

GCD中的FIFO队列称为dispatch queue,它可以保证先进来的任务先得到执行
dispatch queue分为下面三种:

  1. Serial:又称为private dispatch queues,同时只执行一个任务。Serial queue通常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。
  2. Concurrent:又称为global dispatch queue,可以并发地执行多个任务,但是执行完成的顺序是随机的。
  3. Main dispatch queue:它是全局可用的serial queue,它是在应用程序主线程上执行任务的。

dispatch_sync(),同步添加操作。等待添加进队列里面的操作完成之后再继续执行。调用以后等到block执行完以后才返回 ,dispatch_sync()会阻塞当前线程。
dispatch_async ,异步添加进任务队列,调用以后立即返回,它不会做任何等待

在多线程开发当中,程序员只要将想做的事情定义好,并追加到DispatchQueue(派发队列)当中就好了。派发队列分为两种,一种是串行队列(SerialDispatchQueue),一种是并行队列(ConcurrentDispatchQueue)。一个任务就是一个block,比如,将任务添加到队列中的代码是:dispatch_async(queue, block);当给queue添加多个任务时:

  1. queue是串行队列,则它们按顺序一个个执行,同时处理的任务只有一个。
  2. queue是并行队列时,不论第一个任务是否结束,都会立刻开始执行后面的任务,也就是可以同时执行多个任务。但是并行执行的任务数量取决于XNU内核,是不可控的。比如,如果同时执行10个任务,那么10个任务并不是开启10个线程,线程会根据任务执行情况复用,由系统控制。
  1. GCD组
- (void) gcd_group {
        
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    queue = dispatch_get_global_queue(0, 0);
    
    dispatch_queue_t queue_1 = dispatch_queue_create("queue_1", DISPATCH_QUEUE_CONCURRENT);
    queue_1 = dispatch_queue_create("SerialQueue", DISPATCH_QUEUE_SERIAL);
    queue_1 = dispatch_get_global_queue(0, 0);
    queue_1 = queue;
    
    // 方式一
    dispatch_group_async(group, queue, ^{
        NSLog(@"组任务 1 - %@", [NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"组任务 2 - %@", [NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"组任务 3 - %@", [NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"组任务 4 - %@", [NSThread currentThread]);
    });
    
    
    // 方式二
    // 在该方法后面的异步任务会被纳入到队列组的监听范围,进入群组
    // dispatch_group_enter|dispatch_group_leave必须要配对使用
    // 测试 当queue与queue_1 不一致是,组完成会提前完成
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"组任务 1 - %@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"组任务 2 - %@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"组任务 3 - %@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"组任务 4 - %@", [NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    // 第一种拦截方法
    dispatch_group_notify(group, queue_1, ^{
        NSLog(@"组完成 - %@", [NSThread currentThread]);
    });

    //    dispatch_group_notify_f(group, queue, <#void * _Nullable context#>, <#dispatch_function_t  _Nonnull work#>)
    
    /**
     第二种拦截方法
     参数
     1./队列组
     2./时间
     现在 DISPATCH_TIME_NOW    : 传现在不会起到拦截作用,会马上执行
     未来 DISPATCH_TIME_FOREVER: 等到队列组中的所有任务都执行完成后才会触发.也能起到监听队列组的效果
     这个方法时【阻塞】的,队列组内任务不执行完成,下面的代码永远不会执行.
     */
    // DISPATCH_TIME_NOW  DISPATCH_TIME_FOREVER
    dispatch_group_wait(group, DISPATCH_TIME_NOW);
//    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"--验证--");
    
    
    // 执行task
    dispatch_async_f(queue, (__bridge void * _Nullable)(@{@"1":@"2"}), task);
}

void task(void*param){
    
    NSLog(@"%s - %@, param - %@",__func__,[NSThread currentThread], param);
}
  1. GCD栅栏障碍-任务分割 dispatch_barrier_async函数会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在dispatch_barrier_async函数追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。苹果文档中指出,如果使用的是全局队列或者创建的不是并发队列,则dispatch_barrier_async实际上就相当于dispatch_async。
/**
 * 栅栏方法 dispatch_barrier_sync / dispatch_barrier_async
 */
- (void)gcd_barrier {
    
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);//【注意】
    
    dispatch_async(queue, ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_sync(queue, ^{
        // 追加任务2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"2---%@",[NSThread currentThread]);
        }
    });
    
    // dispatch_barrier_sync
    dispatch_barrier_async(queue, ^{
        // 追加任务 barrier
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"barrier---%@",[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        // 追加任务3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"3---%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        // 追加任务4
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"4---%@",[NSThread currentThread]);
        }
    });
}

6.1. 关于定义队列:
在不通位置,虽然定义队列的名称和串并行类型都相同,但队列不同;
即:如果要使用同一个队列,队列必须被持有,然后使用该队列。
如例子中的队列打印:
(lldb) po queue2

(lldb) po queue1

(lldb) po queue3

- (void) fun_test1 {

    dispatch_queue_t queue2 = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);

    [self fd1];
    dispatch_barrier_async(queue2, ^{
     
        for (int i = 0; i < 5 ; i++ ) {
            [NSThread sleepForTimeInterval:0.1];
        }
        NSLog(@"Log==> Test==> crash 2");
    });
    [self fd3];
}

- (void) fd1 {

    dispatch_queue_t queue1 = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_barrier_async(queue1, ^{
     
        for (int i = 0; i < 5 ; i++ ) {
            [NSThread sleepForTimeInterval:0.2];
        }
        NSLog(@"Log==> Test==> crash 11");
    });
}

- (void) fd3 {

    dispatch_queue_t queue3 = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_barrier_async(queue3, ^{
     
        for (int i = 0; i < 5 ; i++ ) {
            [NSThread sleepForTimeInterval:0.1];
        }
        NSLog(@"Log==> Test==> crash 33");
    });
}

/** 
预想打印结果:
 Log==> Test==> crash 11
 Log==> Test==> crash 2
 Log==> Test==> crash 33

实际打印结果:
 Log==> Test==> crash 33
 Log==> Test==> crash 2
 Log==> Test==> crash 11
*/

- (void) fun_test2 {
    
    dispatch_queue_t queue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_barrier_async(queue, ^{

        for (int i = 0; i < 5 ; i++ ) {
            [NSThread sleepForTimeInterval:0.2];
        }
        NSLog(@"Log==> Test==> crash 1");
    });

    dispatch_barrier_async(queue, ^{
     
        for (int i = 0; i < 5 ; i++ ) {
            [NSThread sleepForTimeInterval:0.1];
        }
        NSLog(@"Log==> Test==> crash 2");
    });

    dispatch_barrier_async(queue, ^{

        for (int i = 0; i < 5 ; i++ ) {
            [NSThread sleepForTimeInterval:0.1];
        }
        NSLog(@"Log==> Test==> crash 3");
    });
}

/** 
预想打印结果:
 Log==> Test==> crash 1
 Log==> Test==> crash 2
 Log==> Test==> crash 3

实际打印结果:
 Log==> Test==> crash 1
 Log==> Test==> crash 2
 Log==> Test==> crash 3

*/

7.各种锁
@synchronized:适用线程不多,任务量不大的多线程加锁
NSLock:线程锁,其实NSLock并没有想象中的那么差,不知道大家为什么不推荐使用
dispatch_semaphore_t:使用信号来做加锁,性能提升显著
NSCondition:断言,使用其做多线程之间的通信调用不是线程安全的
NSConditionLock:条件锁,单纯加锁性能非常低,比NSLock低很多,但是可以用来做多线程处理不同任务的通信调用
NSRecursiveLock:递归锁/循环锁,性能出奇的高,但是只能作为递归使用,所以限制了使用场景
NSDistributedLock:分布式锁,MAC系统使用

POSIX(pthread_mutex):底层的api,复杂的多线程处理建议使用,并且可以封装自己的多线程
OSSpinLock:自旋锁,性能也非常高,可惜出现了线程问题,适合较短时间等待case
dispatch_barrier_async/dispatch_barrier_sync:dispatch_barrier_sync比dispatch_barrier_async性能要高,真是大出意外

多线程性能比较

Dispatch Semaphore 提供了三个函数:

  1. dispatch_semaphore_create:创建一个Semaphore并初始化信号的总量
  2. dispatch_semaphore_signal:发送一个信号,让信号总量加1
  3. dispatch_semaphore_wait:当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行;可以使总信号量减1。
信号量的使用前提:确定需要处理哪个线程等待(阻塞),需要哪个线程继续执行,然后使用信号量。

Dispatch Semaphore 在实际开发中主要用于:
保持线程同步,将异步执行任务转换为同步执行任务
保证线程安全,为线程加锁

如:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法。通过引入信号量的方式,等待异步执行任务结果,获取到 tasks,然后再返回该 tasks。

- (void)gcd_semaphoreSync {
    
    NSLog(@"currentThread - %@",[NSThread currentThread]);
    NSLog(@"semaphore - begin");
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    __block int var = 0;
    dispatch_async(queue, ^{
        // 任务1
        [NSThread sleepForTimeInterval:1];
        NSLog(@"1 currentThread - %@",[NSThread currentThread]);
        var = 100;
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore - end,var = %d",var);
}
- (void) gcd_threadSafety {
    
    NSLog(@"currentThread - %@",[NSThread currentThread]);

    __block NSInteger ticketSurplusCount = 10;
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_SERIAL);
     
    dispatch_semaphore_t semaphoreLock = dispatch_semaphore_create(1);//
    NSLock *myLock = [NSLock new];
    NSCondition *myCondition = [NSCondition new];
    
    void(^saleTicketSafe)(void) = ^ {
        
        while (1) {
            
            [myLock lock];
            [myCondition lock];
            @synchronized (self)
            {                    
                 dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);// 相当于加锁                
                if (ticketSurplusCount > 0) {
                    
                    ticketSurplusCount--;
                    NSLog(@"%@", [NSString stringWithFormat:@"余票为:%ld 窗口:%@", (long)ticketSurplusCount, [NSThread currentThread]]);
                    [NSThread sleepForTimeInterval:0.2];
                    dispatch_semaphore_signal(semaphoreLock);// 相当于解锁
                }
                else {
                    
                    NSLog(@"已售完");
                     dispatch_semaphore_signal(semaphoreLock);// 相当于解锁
                    break;
                }
            }
            [myLock unlock];
            [myCondition unlock];
        }
    };
    
    dispatch_async(queue1, ^{
        saleTicketSafe();
    });
    
    dispatch_async(queue2, ^{
        saleTicketSafe();
    });
}

你可能感兴趣的:(iOS多线程总结)