iOS:多线程

基础概念

进程

1、进程是一个具有一定独立功能的程序关于某次数据集合的一次运行活动,它是操作系统分配资源的基本单元.
2、进程是指在系统中正在运行的一个应用程序,就是一段程序的执行过程,我们可以理解为手机上的一个app。
3、每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内,拥有独立运行所需的全部资源。

进程之间则是独⽴的地址空间,并且进程之间的资源是独⽴的。

通过“活动监视器”可以查看Mac系统中所开启的进程,如下图:

线程

1、线程是进程执⾏的最小单元,是处理器调度的基本单位,⼀个进程的所有任务都在线程中执⾏。
2、进程要想执⾏任务,必须得有线程,进程⾄少要有⼀条线程。
3、程序启动会默认开启⼀条线程,这条线程被称为主线程或UI线程。

线程共享本进程的地址空间,并共享本进程的资源如内存、I/O、cpu等。

多线程

1、单核CPU,在同一时间,CPU只能处理1条线程,只有1条线程在执行。
多线程并发执行,其实是CPU快速地在多条线程之间调度(切换)。
如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。
2、多核CPU,各个现场可以分配到不同的CPU内核中进行处理,真正的多任务同时处理。

多线程的目的就是同步执行多个线程,提高运行小效率。

多线程的优点
1、提高程序执行效率
2、提供资源的利用率(CPU、内存)

多线程的缺点:
1、开启线程需要占用一定的内存空间(默认情况下,每一个线程占512KB)
2、开启大量的线程,会占用大量的内存空间,降低程序的性能
3、线程越多,CPU调度线程开销越大(不同的线程中间调度,cpu消耗很大,线程的执行效率也会降低)
4、程序设计越复杂(比如线程间通信、数据共享、数据同步等)

iOS多线程实现方案

pthread(C语言,程序员管理生命周期):
一套通用的多线程API,适用于Unix/Linux/Windows等系统,跨平台、可移植,但是使用难度大,基本不太适用。(这里不做分析)

NSThread(OC语言,程序员管理生命周期):
使用更加面向对象,相对简单,可以直接操作线程对象,偶尔使用。

GCD(C语言,自动管理生命周期):
指在替代NSThread等线程技术,充分利用设备的多核,经常使用。

NSOperation(GCD的封装,管理生命周期):
基于GCD,比GCD多了一些更简单实用的功能,更加面向对象,经常使用。

NSThread

NSThread类是苹果官方提供的管理线程的类,我们可以手动开辟的子线程,需要自己管理内存。

常用方法

初始化方法

    NSThread *thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(threadAction) object:nil];
    thread1.name = @"thread1";
    [thread1 start];
    
    NSThread *thread2 = [[NSThread alloc]initWithBlock:^{
        NSLog(@"thread2 action ,currentThread = %@", [NSThread currentThread]);
    }];
    [thread2 start];
    
    [NSThread detachNewThreadSelector:@selector(threadAction) toTarget:self withObject:nil];
    [NSThread detachNewThreadWithBlock:^{
        NSLog(@"detach action, currentThread = %@", [NSThread currentThread]);
    }];
- (void)threadAction {
    NSLog(@"%s cueerntThread = %@",__func__,[NSThread currentThread]);
}
-[ViewController threadAction] cueerntThread = <NSThread: 0x60000285d380>{number = 6, name = thread1}
detach action, currentThread = <NSThread: 0x600002875300>{number = 9, name = (null)}
thread2 action ,currentThread = <NSThread: 0x60000285ebc0>{number = 7, name = (null)}
-[ViewController threadAction] cueerntThread = <NSThread: 0x60000285cfc0>{number = 8, name = (null)}

注意alloc初始化的NSThread需要手动调用start方法,detachNewThread初始化的不需要。

线程间通信

线程间通信常用的方法
// 去主线程执行指定的方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

// waitUntilDone:是否将该回调方法执行完在执行后面的代码,
// 如果为YES:就必须等回调方法执行完成之后才能执行后面的代码,说白了就是阻塞当前的线程;
// 如果是NO:就是不等回调方法结束,不会阻塞当前线程

//去指定的线程执行指定的方法
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

// 开启的线程在后台执行
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;
NSThread *thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(threadAction) object:nil];
thread1.name = @"thread1";
[thread1 start];

- (void)threadAction {
    NSLog(@"%s cueerntThread = %@",__func__,[NSThread currentThread]);
    
    sleep(3);
    // 方法1
    [self performSelectorOnMainThread:@selector(mainThreadAction) withObject:nil waitUntilDone:YES];
    // 方法2
    [self performSelector:@selector(refreshUI) onThread:[NSThread mainThread] withObject:nil waitUntilDone:YES];

}

- (void)mainThreadAction {
    NSLog(@"%s cueerntThread = %@",__func__,[NSThread currentThread]);

}
- (void)refreshUI {
    NSLog(@"%s cueerntThread = %@",__func__,[NSThread currentThread]);
}

打印输出

-[ViewController threadAction] cueerntThread = <NSThread: 0x6000026acc00>{number = 6, name = thread1}
-[ViewController mainThreadAction] cueerntThread = <_NSMainThread: 0x6000026e8340>{number = 1, name = main}
-[ViewController refreshUI] cueerntThread = <_NSMainThread: 0x6000026e8340>{number = 1, name = main}

可知:thread1的方法中可以再回到主线程/其他线程继续进行操作

注意:performSelector,若使用带afterDelay的延时函数,内部创建一个 NSTimer,然后添加到当前线程的Runloop中。也就是如果当前线程没有开启runloop,该方法会失效。在子线程中,需要启动runloop。

其他常用方法

[NSThread currentThread]; // 获取当前线程
[NSThread mainThread]; // 获取主线程
[NSThread isMainThread]; //判断是不是主线程

在Runloop章节中

实现一个常驻线程:
1、获取/创建当前线程的RunLoop;
2、向该RunLoop中添加一个Source/Port等来维持RunLoop的事件循环(如果 Mode 里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出);
3、启动该RunLoop。

GCD

Grand Central Dispatch技术,GCD引入任务和队列,把任务放在队列中执行,指定任务和队列的类型,不需要关心线程的操作(创建、销毁、调度),大大简化了开发难度。

GCD的任务和队列

任务

在线程中执行的那段代码,GCD是block块形式。
执行任务有两种方式:同步和异步。

同步任务:
指代码按照顺序执行,前一个任务结束后才开始下一个任务。
这种模式下,程序的执行是阻塞的,在等待某个结果或者处理完毕时,程序的其他部分是不能进行其他工作的。
不具备开启线程的能力,始终在当前线程中执行。

异步任务:
指代码在执行时不必等待上一个任务结束,可以同时进行多个任务。
当异步任务执行完毕后,可以通过回调、通知或者其他机制通知调用者。
这种模式下,程序的执行是非阻塞的,可以在等待任务完成的同时执行其他任务,提高程序的执行效率。
具备开启线程的能力,在新线程执行任务。

// 同步任务
void
dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block)
// 异步任务
void
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

队列

是管理任务的一个线性数据结构。
采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。
每读取一个任务,则从队列中释放一个任务。

队列有两种:串行和并发

串行:
一个任务执行完毕,再执行下一个任务。

并发:
可以多个任务同时执行。

// 串行队列
dispatch_queue_t serialqueue = dispatch_queue_create("SERIAL.QUEUE", DISPATCH_QUEUE_SERIAL);

// 并发队列
dispatch_queue_t concurrentqueue = dispatch_queue_create("CONCURRENT.QUEUE", DISPATCH_QUEUE_CONCURRENT);

// 主队列 - 串行
dispatch_queue_t mainQueue = dispatch_get_main_queue();

// 全局队列 - 并发
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

任务和队列组合:

1、同步+并发队列

// 同步+并发
dispatch_sync(concurrentqueue, ^{
    NSLog(@"dispatch_sync1 concurrentqueue- cueerntThread = %@",[NSThread currentThread]);
});

dispatch_sync(concurrentqueue, ^{
    NSLog(@"dispatch_sync2 concurrentqueue- cueerntThread = %@",[NSThread currentThread]);
});

输出:
dispatch_sync1 concurrentqueue- cueerntThread = <_NSMainThread: 0x6000026fc480>{number = 1, name = main}
dispatch_sync2 concurrentqueue- cueerntThread = <_NSMainThread: 0x6000026fc480>{number = 1, name = main}

没有开启新线程,顺序执行任务

2、同步+串行队列

 // 同步+串行
dispatch_sync(serialqueue, ^{
    NSLog(@"dispatch_sync1 serialqueue- cueerntThread = %@",[NSThread currentThread]);
});

dispatch_sync(serialqueue, ^{
    NSLog(@"dispatch_sync2 serialqueue- cueerntThread = %@",[NSThread currentThread]);
});

输出:
dispatch_sync1 serialqueue- cueerntThread = <_NSMainThread: 0x6000026fc480>{number = 1, name = main}
dispatch_sync2 serialqueue- cueerntThread = <_NSMainThread: 0x6000026fc480>{number = 1, name = main}

没有开启新线程,顺序执行任务。

3、同步+主队列

- (void)viewDidLoad {
  // 同步+主队列
    dispatch_sync(mainQueue, ^{
        NSLog(@"dispatch_sync mainQueue- cueerntThread = %@",[NSThread currentThread]);
    });
}

死锁。
原因:队列引起的循环等待导致死锁
(viewDidLoad是在主队列中的,dispatch_sync也是添加在主队列中,导致互相等待)

// 同步+串行
dispatch_sync(serialqueue, ^{
   NSLog(@"dispatch_sync1 serialqueue- cueerntThread = %@",[NSThread currentThread]);
   
   dispatch_sync(serialqueue1, ^{
       NSLog(@"dispatch_sync2 serialqueue- cueerntThread = %@",[NSThread currentThread]);
   });
});
这种情况不会死锁。因为不是同一个queue。

4、异步+并发队列

// 异步+并发
dispatch_async(concurrentqueue, ^{
    NSLog(@"dispatch_async1 concurrentqueue- cueerntThread = %@",[NSThread currentThread]);
});
dispatch_async(concurrentqueue, ^{
    NSLog(@"dispatch_async2 concurrentqueue- cueerntThread = %@",[NSThread currentThread]);
});
for(NSInteger i = 0; i < 5; i++){
    // 异步+并发
    dispatch_async(concurrentqueue, ^{
        NSLog(@"i = %ld ,dispatch_async concurrentqueue- cueerntThread = %@",i,[NSThread currentThread]);
    });
}

输出:
i = 1 ,dispatch_async concurrentqueue- cueerntThread = <NSThread: 0x600000d833c0>{number = 5, name = (null)}
dispatch_async2 concurrentqueue- cueerntThread = <NSThread: 0x600000dfda80>{number = 8, name = (null)}
i = 0 ,dispatch_async concurrentqueue- cueerntThread = <NSThread: 0x600000d83800>{number = 6, name = (null)}
dispatch_async1 concurrentqueue- cueerntThread = <NSThread: 0x600000d84b00>{number = 4, name = (null)}
i = 2 ,dispatch_async concurrentqueue- cueerntThread = <NSThread: 0x600000d99640>{number = 3, name = (null)}
i = 3 ,dispatch_async concurrentqueue- cueerntThread = <NSThread: 0x600000dcc5c0>{number = 7, name = (null)}
i = 4 ,dispatch_async concurrentqueue- cueerntThread = <NSThread: 0x600000dc8980>{number = 9, name = (null)}

开启多条线程,并发执行任务。

5、异步+串行队列

// 异步+串行
dispatch_async(serialqueue, ^{
    NSLog(@"dispatch_async1 serialqueue- cueerntThread = %@",[NSThread currentThread]);
});
dispatch_async(concurrentqueue, ^{
    NSLog(@"dispatch_async2 serialqueue- cueerntThread = %@",[NSThread currentThread]);
});
for(NSInteger i = 0; i < 5; i++){
    // 异步+串行
    dispatch_async(serialqueue, ^{
        NSLog(@"i = %ld ,dispatch_async serialqueue- cueerntThread = %@",i,[NSThread currentThread]);
    });
}

输出:
dispatch_async2 serialqueue- cueerntThread = <NSThread: 0x600001040540>{number = 5, name = (null)}
dispatch_async1 serialqueue- cueerntThread = <NSThread: 0x6000010096c0>{number = 6, name = (null)}
i = 0 ,dispatch_async serialqueue- cueerntThread = <NSThread: 0x6000010096c0>{number = 6, name = (null)}
i = 1 ,dispatch_async serialqueue- cueerntThread = <NSThread: 0x6000010096c0>{number = 6, name = (null)}
i = 2 ,dispatch_async serialqueue- cueerntThread = <NSThread: 0x6000010096c0>{number = 6, name = (null)}
i = 3 ,dispatch_async serialqueue- cueerntThread = <NSThread: 0x6000010096c0>{number = 6, name = (null)}
i = 4 ,dispatch_async serialqueue- cueerntThread = <NSThread: 0x6000010096c0>{number = 6, name = (null)}

开启新的线程,顺序执行任务。

6、异步+主队列

// 异步+主队列
dispatch_async(mainQueue, ^{
    NSLog(@"dispatch_async1 mainQueue- cueerntThread = %@",[NSThread currentThread]);
});
dispatch_async(mainQueue, ^{
    NSLog(@"dispatch_async2 mainQueue- cueerntThread = %@",[NSThread currentThread]);
});
for(NSInteger i = 0; i < 5; i++){
    // 异步+主队列
    dispatch_async(mainQueue, ^{
        NSLog(@"i = %ld ,dispatch_async mainQueue- cueerntThread = %@",i,[NSThread currentThread]);
    });
}

输出:
dispatch_async1 mainQueue- cueerntThread = <_NSMainThread: 0x600001b8c000>{number = 1, name = main}
dispatch_async2 mainQueue- cueerntThread = <_NSMainThread: 0x600001b8c000>{number = 1, name = main}
i = 0 ,dispatch_async mainQueue- cueerntThread = <_NSMainThread: 0x600001b8c000>{number = 1, name = main}
i = 1 ,dispatch_async mainQueue- cueerntThread = <_NSMainThread: 0x600001b8c000>{number = 1, name = main}
i = 2 ,dispatch_async mainQueue- cueerntThread = <_NSMainThread: 0x600001b8c000>{number = 1, name = main}
i = 3 ,dispatch_async mainQueue- cueerntThread = <_NSMainThread: 0x600001b8c000>{number = 1, name = main}
i = 4 ,dispatch_async mainQueue- cueerntThread = <_NSMainThread: 0x600001b8c000>{number = 1, name = main}

未开启新的线程,顺序执行任务。

结论:

线程间通信

dispatch_async(concurrentqueue, ^{
    NSLog(@"dispatch_async1 mainQueue- concurrentqueue = %@",[NSThread currentThread]);
    sleep(3);
    NSLog(@"sleep over  time ");
    //回到主线程
    dispatch_async(mainQueue, ^{
        // 追加在主线程中执行的任务
        NSLog(@"enter  mainQueue");
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    });
});

输出:
17:05:20.426577+0800 -  dispatch_async1 mainQueue- concurrentqueue = <NSThread: 0x600003b89280>{number = 7, name = (null)}
17:05:23.427508+0800 -  sleep over  time
17:05:23.427917+0800 -  enter  mainQueue
17:05:25.428755+0800 -  2---<_NSMainThread: 0x600003bcc0c0>{number = 1, name = main}

GCD常用方法

1、dispatch_barrier_async

有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于 【栅栏 一样的】一个方法将两组异步执行的操作组给分割起来,操作组里可以包含一个或多个任务。
这就需要用到dispatch_barrier_async 方法在两个操作组间形成栅栏。
dispatch_barrier_async 方法会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。
然后在 dispatch_barrier_async 方法追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。

多用于实现多读单写。

// dispatch_barrier_async
- (void)barrier {
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(concurrentQueue, ^{
        // 追加任务 1
        sleep(3);
        NSLog(@"1---%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        // 追加任务 2
        sleep(1);
        NSLog(@"2---%@",[NSThread currentThread]);
    });
    
    dispatch_barrier_async(concurrentQueue, ^{
        // 追加任务 barrier
        sleep(3);
        NSLog(@"barrier---%@",[NSThread currentThread]);
    });
    
    dispatch_async(concurrentQueue, ^{
        // 追加任务 3
        sleep(2);
        NSLog(@"3---%@",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        // 追加任务 4
        sleep(4);
        NSLog(@"4---%@",[NSThread currentThread]);
    });
}

输出:
17:16:45.413302+0800  2---<NSThread: 0x600002c50c40>{number = 6, name = (null)}
17:16:47.413905+0800  1---<NSThread: 0x600002c4c5c0>{number = 5, name = (null)}
17:16:50.418616+0800  barrier---<NSThread: 0x600002c4c5c0>{number = 5, name = (null)}
17:16:52.423121+0800  3---<NSThread: 0x600002c4c5c0>{number = 5, name = (null)}
17:16:54.423306+0800  4---<NSThread: 0x600002c50c40>{number = 6, name = (null)}

在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作。

多读单写实现:

dispatch_after

dispatch_after 方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。考虑到队列阻塞等情况,这个任务延迟执行的时间是不准确的,但想要大致延迟执行任务,dispatch_after 方法是很有效的。

//dispatch_after
- (void)after {
    NSLog(@"begin - currentThread---%@",[NSThread currentThread]);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 2.0 秒后异步追加任务代码到主队列,并开始执行
        NSLog(@"after - currentThread---%@",[NSThread currentThread]);
    });
}

输出:
17:22:20.238944+0800  begin - currentThread---<_NSMainThread: 0x6000016fc480>{number = 1, name = main}
17:22:22.304399+0800  after - currentThread---<_NSMainThread: 0x6000016fc480>{number = 1, name = main}

dispatch_once

在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 方法。

使用 dispatch_once 方法能保证某段代码在程序运行过程中只被执行 1 次,并且即使在多线程的环境下,dispatch_once 也可以保证线程安全。

// dispatch_once
- (void)onceTest {
    for (int i = 0; i < 10; i++) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSLog(@"%@",[NSThread currentThread]);
        });
        NSLog(@"i = %d",i);
    }
}

输出:
<_NSMainThread: 0x600003bf04c0>{number = 1, name = main}
i = 0
i = 1
.....
i = 9

dispatch_apply

类似一个 for 循环。dispatch_apply 按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。

串行队列中使用,不会开启子线程,会和for循环一样在主线程顺序执行。

并发队列中使用,dispatch_apply 可以 在多个线程中同时(异步)遍历。

// - dispatch_apply
- (void)apply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    NSLog(@"apply---begin");
    dispatch_apply(5, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"apply---end");
}

输出:
apply---begin
0---<_NSMainThread: 0x6000024ac480>{number = 1, name = main}
1---<_NSMainThread: 0x6000024ac480>{number = 1, name = main}
2---<_NSMainThread: 0x6000024ac480>{number = 1, name = main}
3---<_NSMainThread: 0x6000024ac480>{number = 1, name = main}
4---<_NSMainThread: 0x6000024ac480>{number = 1, name = main}
apply---end

并发队列中异步执行任务,所以各个任务的执行时间长短不定,最后结束顺序也不定,但是 apply---end 一定在最后执行。

这是因为 dispatch_apply 方法会等待全部任务执行完毕。

dispatch_group

GCD队列组,当我们想在gcd queue中所有的任务执行完毕之后做些特定事情的时候,也就是队列的同步问题,如果队列是串行的话,那将该操作最后添加到队列中即可,但如果队列是并行队列的话,这时候就可以利用dispatch_group来实现了。

常用方法:
// 创建队列组
dispatch_group_create

// 异步执行一个block,并与指定的队列组关联
dispatch_group_async

//等待先前 dispatch_group_async 添加的 block 都执行完毕以后,
// 将 dispatch_group_notify 中的 block 提交到指定队列。
dispatch_group_notify

// 等待先前 dispatch_group_async 添加的 block 都执行完毕,才继续往下执行。
// 阻塞当前线程
dispatch_group_wait

//标志一个任务加入到队列组
dispatch_group_enter
// 一个任务离开队列组
dispatch_group_leave

- (void)groupNotify {
    NSLog(@"begin - %@",[NSThread currentThread]);
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_async(group, concurrentQueue, ^{
        // 追加任务 1
        NSLog(@"1-begin--%@",[NSThread currentThread]);
        sleep(3);
        NSLog(@"1-end--%@",[NSThread currentThread]);
    });
       
    dispatch_group_async(group, concurrentQueue, ^{
        // 追加任务 2
        NSLog(@"2-begin--%@",[NSThread currentThread]);
        sleep(2);
        NSLog(@"2-end--%@",[NSThread currentThread]);
    });
       
  
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
       // 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
        NSLog(@"group---begin - 3---%@",[NSThread currentThread]);
        sleep(1);
        NSLog(@"group---end - 3---%@",[NSThread currentThread]);
    });
//    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    NSLog(@"end");
}

输出:
begin - <_NSMainThread: 0x600000d48480>{number = 1, name = main}
end
2-begin--<NSThread: 0x600000d786c0>{number = 7, name = (null)}
1-begin--<NSThread: 0x600000d10040>{number = 6, name = (null)}
2-end--<NSThread: 0x600000d786c0>{number = 7, name = (null)}
1-end--<NSThread: 0x600000d10040>{number = 6, name = (null)}
group---begin - 3---<_NSMainThread: 0x600000d48480>{number = 1, name = main}
group---end - 3---<_NSMainThread: 0x600000d48480>{number = 1, name = main}

若执行dispatch_group_wait,输出为:
begin - <_NSMainThread: 0x6000038a8000>{number = 1, name = main}
2-begin--<NSThread: 0x6000038f87c0>{number = 4, name = (null)}
1-begin--<NSThread: 0x6000038a3880>{number = 7, name = (null)}
2-end--<NSThread: 0x6000038f87c0>{number = 4, name = (null)}
1-end--<NSThread: 0x6000038a3880>{number = 7, name = (null)}
end
- (void)notiftyEnterLeave {
    NSLog(@"begin - %@",[NSThread currentThread]);
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
   
    dispatch_group_enter(group);
    dispatch_async(concurrentQueue, ^{
        // 追加任务 1
        NSLog(@"1-begin--%@",[NSThread currentThread]);
        sleep(3);
        NSLog(@"1-end--%@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(concurrentQueue, ^{
        // 追加任务 2
        NSLog(@"2-begin--%@",[NSThread currentThread]);
        sleep(3);
        NSLog(@"2-end--%@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
       // 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
        NSLog(@"group---begin - 3---%@",[NSThread currentThread]);
        sleep(1);
        NSLog(@"group---end - 3---%@",[NSThread currentThread]);
    }); 
}

输出:
begin - <_NSMainThread: 0x600002c14000>{number = 1, name = main}
1-begin--<NSThread: 0x600002c12540>{number = 6, name = (null)}
2-begin--<NSThread: 0x600002c54840>{number = 4, name = (null)}
2-end--<NSThread: 0x600002c54840>{number = 4, name = (null)}
1-end--<NSThread: 0x600002c12540>{number = 6, name = (null)}
group---begin - 3---<_NSMainThread: 0x600002c14000>{number = 1, name = main}
group---end - 3---<_NSMainThread: 0x600002c14000>{number = 1, name = main}

根据运行代码可知:

dispatch_group_enter、dispatch_group_leave 组合,等同于dispatch_group_async。

dispatch_semaphore

GCD 信号量dispatch_semaphore可以用来控制最大并发数量,可以用来实现 iOS 的线程同步方案。

信号量的初始值,可以用来控制线程并发访问的最大数量;
信号量的初始值为1,代表同时只允许 1 条线程访问资源,保证线程同步。
若设置信号量初始值为3.则控制最大并发数为3。

// 创建一个 Semaphore 并初始化信号的总量
dispatch_semaphore_create

发送一个信号,让信号总量加 1
dispatch_semaphore_signal

可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
dispatch_semaphore_wait

- (void)semaphoreAction {
    
    NSLog(@"begin - currentThread---%@",[NSThread currentThread]);
    
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    __block int number = 0;
    dispatch_async(concurrentQueue, ^{
        // 追加任务 1
        sleep(2);
        NSLog(@"1---%@",[NSThread currentThread]);
        number = 100;
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore---end,number = %d",number);

}

输出:
begin - currentThread---<_NSMainThread: 0x600002e80600>{number = 1, name = main}
semaphore-before - wait--
1---<NSThread: 0x600002eccfc0>{number = 6, name = (null)}
semaphore---end,number = 100

执行过程为:
1、semaphore 初始创建时计数为 0。
2、异步执行 将 任务 1 追加到队列之后,不做等待,继续向下执行
3、执行dispatch_semaphore_wait,semaphore 减 1
此时semaphore -1,当前线程进入等待状态。
4、然后任务1开始执行,当执行到dispatch_semaphore_signal方法,semaphore 加1,
此时 semaphore 0,正在被阻塞的线程(主线程)恢复继续执行。
5、最后打印出semaphore---end,number = 100

// 设置最大并发量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);

for(int i = 0; i<10;i++) {
    [[[NSThread alloc]initWithBlock:^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        sleep(5);
        NSLog(@"执行任务 i = %d,%@",i,[NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);
    }] start];
}

输出:
18:20:28.610662+0800  执行任务 i = 2,<NSThread: 0x60000309c9c0>{number = 9, name = (null)}
18:20:28.610662+0800  执行任务 i = 1,<NSThread: 0x60000309c300>{number = 8, name = (null)}
18:20:28.610697+0800  执行任务 i = 0,<NSThread: 0x60000309c400>{number = 7, name = (null)}

18:20:33.613855+0800  执行任务 i = 6,<NSThread: 0x60000309d480>{number = 13, name = (null)}
18:20:33.613856+0800  执行任务 i = 3,<NSThread: 0x60000309c000>{number = 10, name = (null)}
18:20:33.613856+0800  执行任务 i = 5,<NSThread: 0x60000309ccc0>{number = 12, name = (null)}

18:20:38.614500+0800  执行任务 i = 8,<NSThread: 0x60000309c380>{number = 15, name = (null)}
18:20:38.614502+0800  执行任务 i = 9,<NSThread: 0x60000309c0c0>{number = 16, name = (null)}
18:20:38.614501+0800  执行任务 i = 4,<NSThread: 0x60000309c040>{number = 11, name = (null)}

18:20:43.619216+0800  执行任务 i = 7,<NSThread: 0x60000309d0c0>{number = 14, name = (null)}

注意:输出的时间间隔。

NSOperation

NSOperation 是苹果对GCD面向对象的封装,它的底层是基于GCD实现的,
相比于GCD它添加了更多实用的功能可以:
1、添加任务依赖
2、任务执行状态的控制
3、设置最大并发数

两个核心类分别是NSOperation和NSOperationQueue,NSOperation是对任务进行的封装,封装好的任务交给不同的NSOperationQueue即可进行串行队列的执行或并发队列的执行。

NSOperation

NSOperation是一个抽象类,并不能直接使用,必须使用它的子类,
有三种方式:NSInvocationOperation、NSBlockOperation、自定义子类继承NSOperation,
前两种是苹果为我们封装好的,可以直接使用,
自定义子类,需要我们实现相应的方法。
NSOperation 单独使用时是同步执行操作,配合 NSOperationQueue 才能实现异步执行。

NSBlockOperation和NSInvocationOperation

- (void)addOperation {
    //创建一个NSBlockOperation对象,传入一个block
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++)
        {
            NSLog(@"Task1 %@ %d", [NSThread currentThread], i);
        }
    }];
  
    [operation start];

    /*
    创建一个NSInvocationOperation对象,指定执行的对象和方法
    该方法可以接收一个参数即object
    */
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationAction:) object:@"Hello, World!"];
    [invocationOperation start];

}
- (void)invocationOperationAction:(NSString *)message {
    NSLog(@"message = %@, currentThread = %@",message,[NSThread currentThread]);
}

输出:
Task1 <_NSMainThread: 0x600002db03c0>{number = 1, name = main} 0
Task1 <_NSMainThread: 0x600002db03c0>{number = 1, name = main} 1
Task1 <_NSMainThread: 0x600002db03c0>{number = 1, name = main} 2
message = Hello, World!, currentThread = <_NSMainThread: 0x600002db03c0>{number = 1, name = main}

由上述代码可知:
NSInvocationOperation、NSBlockOperation单独使用时,并没有开启新的线程,任务都是在当前线程中执行的。

但是NSBlockOperation类中还提供一个addExecutionBlock方法,这个方法可以添加一个代码执行块,使用addExecutionBlock追加的任务是并发执行的,如果这个操作的任务数大于1那么会开启子线程并发执行任务,这里追加的任务不一定就是子线程,也有可能是主线程。

- (void)addOperation {
    //创建一个NSBlockOperation对象,传入一个block
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++)
        {
            NSLog(@"Task1 i = %d, %@ ",i, [NSThread currentThread]);
        }
    }];
    
    [operation addExecutionBlock:^{
        NSLog(@"Task2=====%@",[NSThread currentThread]);
    }];
        
    [operation addExecutionBlock:^{
        NSLog(@"Task3=====%@",[NSThread currentThread]);
    }];
    [operation start];

    /*
    创建一个NSInvocationOperation对象,指定执行的对象和方法
    该方法可以接收一个参数即object
    */
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationAction:) object:@"Hello, World!"];
    [invocationOperation start];

}

输出:
Task1 i = 0, <_NSMainThread: 0x600000ab0040>{number = 1, name = main}
Task2=====<NSThread: 0x600000a8e4c0>{number = 7, name = (null)}
Task3=====<NSThread: 0x600000ae03c0>{number = 4, name = (null)}
2024-04-23 21:35:58.455815+0800 HFTestDemo[37272:26405918] Task1 i = 1, <_NSMainThread: 0x600000ab0040>{number = 1, name = main}
2024-04-23 21:35:58.455946+0800 HFTestDemo[37272:26405918] Task1 i = 2, <_NSMainThread: 0x600000ab0040>{number = 1, name = main}
2024-04-23 21:35:58.456551+0800 HFTestDemo[37272:26405918] message = Hello, World!, currentThread = <_NSMainThread: 0x600000ab0040>{number = 1, name = main}

自定义的NSOperation

如果使用子类 NSInvocationOperation、NSBlockOperation不能满足日常需求,我们还可以自定义子类。

定一个类继承自NSOperation,重写它的main或者start方法便可。

备注:自定义的NSOperation单独使用也是不开启线程的。需要配合queue使用实现并发

重写main方法

只重写operation的main方法,main方法里面写要执行的任务

系统底层控制变更任务执行完成状态,以及任务的退出。


@interface HFCustomOperation : NSOperation
@property (nonatomic, assign) int sleepSeconds;
@property (nonatomic,copy) void(^finishBlock)(void);
@end

@implementation HFCustomOperation
- (void)main {
    NSLog(@"%s , thread===%@",__func__,[NSThread currentThread]);
    sleep(self.sleepSeconds);
    if(self.finishBlock) {
        self.finishBlock();
    }
}
- (id)init {
    if(self = [super init]) {
        _sleepSeconds = 2;
    }
    return self;
}
@end
- (void)addCustomOperation {
    HFCustomOperation *customOperation1 = [[HFCustomOperation alloc]init];
    customOperation1.sleepSeconds = 3;
    customOperation1.finishBlock = ^{
        NSLog(@"customOperation1 - currentThread = %@ ",[NSThread currentThread]);
    };
    HFCustomOperation *customOperation2 = [[HFCustomOperation alloc]init];
    customOperation2.finishBlock = ^{
        NSLog(@"customOperation2 - currentThread = %@ ",[NSThread currentThread]);
    };
    [customOperation1 start];
    [customOperation2 start];
}

输出:
10:52:44.491744+0800 -[HFCustomOperation main] , thread===<_NSMainThread: 0x600003b30580>{number = 1, name = main}
10:52:47.493039+0800 customOperation1 - currentThread = <_NSMainThread: 0x600003b30580>{number = 1, name = main}
10:52:47.493256+0800 -[HFCustomOperation main] , thread===<_NSMainThread: 0x600003b30580>{number = 1, name = main}
10:52:49.493481+0800 customOperation2 - currentThread = <_NSMainThread: 0x600003b30580>{number = 1, name = main}
NSOperationQueue *queues = [[NSOperationQueue alloc] init];
[queues addOperations:@[customOperation1,customOperation2] waitUntilFinished:NO];

输出:
10:54:55.029612+0800 -[HFCustomOperation main] , thread===<NSThread: 0x600000e4adc0>{number = 4, name = (null)}
10:54:55.029622+0800 -[HFCustomOperation main] , thread===<NSThread: 0x600000e7a400>{number = 6, name = (null)}
10:54:57.032018+0800 customOperation2 - currentThread = <NSThread: 0x600000e4adc0>{number = 4, name = (null)}
10:54:58.033836+0800 customOperation1 - currentThread = <NSThread: 0x600000e7a400>{number = 6, name = (null)}

添加到NSOperationQueue后,就实现了并发。

重写start方法

如果不想使用queue,还想实现并发操作,可以通过重写start方法实现。
重写start方法需要自行控制任务状态。

start:把需要执行的任务放在start方法里,任务加到队列后,队列会管理任务并在线程被调度后,调用start方法,不需要调用父类的方法

还需要重写下面几个属性:
asynchronous:表示是否并发执行
executing:表示任务是否正在执行,需要手动调用KVO方法来进行通知,方便其他类监听了任务的该属性
finished:表示任务是否结束,需要手动调用KVO方法来进行通知,队列也需要监听改属性的值,用于判断任务是否结束

@interface HFCustomOperation ()
@property (nonatomic, assign, getter=isExecuting) BOOL executing;
@property (nonatomic, assign, getter=isFinished) BOOL finished;
@end

@implementation HFCustomOperation
@synthesize executing = _executing;
@synthesize finished = _finished;

- (id)init {
    if(self = [super init]) {
        _sleepSeconds = 2;
    }
    return self;
}
- (void)dealloc {
    NSLog(@"%s - name = %@",__func__,self.name);
}

//- (void)main {
//    NSLog(@"%s , thread===%@",__func__,[NSThread currentThread]);
//    sleep(self.sleepSeconds);
//    if(self.finishBlock) {
//        sleep(2);
//        self.finishBlock();
//    }
//}

- (void)start {
    //任务开始之前设置executing
    self.executing = YES;
    NSLog(@"开始执行任务 name = %@ - thread===%@",self.name,[NSThread currentThread]);
    
    if(self.isCancelled) {
        //任务被取消 结束前手动设置状态
        self.executing = NO;
        self.finished = YES;
        return;;
    }
    // 开始执行复杂的耗时操作
    __weak typeof(self) weakSelf = self;
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue, ^{
        // 模拟任务 1
        sleep(3);
        NSLog(@"task 完成-name = %@--%@",weakSelf.name,[NSThread currentThread]);
        weakSelf.executing = NO;
        weakSelf.finished = YES;
        if(weakSelf.finishBlock) {
            weakSelf.finishBlock();
        }
    });
}
- (void)setExecuting:(BOOL)executing {
    //  手动调用kvo通知
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}
- (BOOL)isExecuting {
    return _executing;
}

- (void)setFinished:(BOOL)finished {
    //  手动调用kvo通知
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}
- (BOOL)isAsynchronous {
    return YES;
}

@end
- (void)addCustomOperation {
    HFCustomOperation *customOperation1 = [[HFCustomOperation alloc]init];
    customOperation1.name = @"one";
    customOperation1.sleepSeconds = 3;
    customOperation1.finishBlock = ^{
        NSLog(@"customOperation1 - currentThread = %@ ",[NSThread currentThread]);
    };

    HFCustomOperation *customOperation2 = [[HFCustomOperation alloc]init];
    customOperation2.name = @"two";

    customOperation2.finishBlock = ^{
        NSLog(@"customOperation2 - currentThread = %@ ",[NSThread currentThread]);
    };

    NSOperationQueue *queues = [[NSOperationQueue alloc] init];
    [queues addOperations:@[customOperation1,customOperation2] waitUntilFinished:NO];
    
}

输出:
12:14:45 开始执行任务 name = one - thread===<NSThread: 0x600001d25b80>{number = 6, name = (null)}
12:14:48 task 完成-name = one--<NSThread: 0x600001d2e400>{number = 3, name = (null)}
12:14:48 task 完成-name = two--<NSThread: 0x600001d25b80>{number = 6, name = (null)}
12:14:48 -[HFCustomOperation dealloc] - name = two
12:14:48 -[HFCustomOperation dealloc] - name = one

**如何开始自定义一个并发的NSOperation? **
1、在NSOperation类文件中将以下两个属性粘贴过来:
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;

2、将readonly改为readwrite
@property (readwrite, getter=isExecuting) BOOL executing;
@property (readwrite, getter=isFinished) BOOL finished;
并通过@synthesize添加别名, 使其可以添加setter方法
@synthesize executing = _executing;
@synthesize finished = _finished;

3、为executing/finished 两个属性添加 setter/getter方法,
setter方法应使用kvo通知(NSOperationQueue会监听这两个属性的kvo通知)
以executing 属性为例

-(void)setExecuting:(BOOL)executing {
// 手动调用kvo通知
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}

-(BOOL)isExecuting {
return _executing;
}

4、重写-(BOOL)isAsynchronous;方法,返回YES
-(BOOL)isAsynchronous{ return YES; }

5、重写-(void)start;方法,
开始任务时:self.executing = YES;
结束任务时:
self.executing = NO;
self.finished = YES;
具体实现看示例代码

@synthesize 的作用:
给实例变量起一个别名,或者说为同一个变量添加两个名字
使用场景1: 为一个属性同时添加getter和setter方法
使用场景2: 为一个只读属性添加setter方法

查看第三方库SDWebImage 了解更多自定义NSOperation。

NSOperationQueue

NSOperationQueue 有两种队列,
一个是主队列通过[NSOperationQueue mainQueue] 获取,
凡是添加到主队列中的任务都会放到主线程中执行。

还有一个是自己创建的队列[[NSOperationQueue alloc] init],
它包含了串行和并发两种不同的功能,可以通过设置最大并发数来决定。
凡是添加到自定义队列中的任务会自动放到子线程中执行。

- (void)addOperation {
    //创建一个NSBlockOperation对象,传入一个block
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++)
        {
            NSLog(@"Task1 i = %d, %@ ",i, [NSThread currentThread]);
        }
    }];

    /*
    创建一个NSInvocationOperation对象,指定执行的对象和方法
    该方法可以接收一个参数即object
    */
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationAction:) object:@"Hello, World!"];
   
    NSOperationQueue *queues = [[NSOperationQueue alloc] init];
    //设置最大并发数,如果设置为1则串行执行
    [queues setMaxConcurrentOperationCount:2];
    [queues addOperation:operation];
    [queues addOperation:invocationOperation];
}

输出:
message = Hello, World!, currentThread = <NSThread: 0x600002d17840>{number = 6, name = (null)}
Task1 i = 0, <NSThread: 0x600002d0dd40>{number = 4, name = (null)}
Task1 i = 1, <NSThread: 0x600002d0dd40>{number = 4, name = (null)}
Task1 i = 2, <NSThread: 0x600002d0dd40>{number = 4, name = (null)}

这个例子有两个任务,如果设置最大并发数为2,则会开辟两个线程,并发执行这两个任务。如果设置为1,则会在新的线程中串行执行。

还可以使用[queues addOperations:@[operation, invocationOperation] waitUntilFinished:NO]; 同时添加多个任务。

NSOperationQueue还提供了一个addOperationWithBlock方法可以将operation对象添加到NSOperationQueue中。

决定NSOperationQueue并发执行还是同步执行的关键就在于最大并发任务数maxConcurrentOperationCount。(默认数值-1)

maxConcurrentOperationCount为1时,同步执行
maxConcurrentOperationCount大于1时,并发执行。
maxConcurrentOperationCount的数值并不是并发线程的数量,而是在一个队列中能够同时执行的任务数量。

任务依赖

NSOperation最大的亮点莫过于可以添加任务之间的依赖关系。所谓的依赖关系就是任务A需要等待任务B完成之后才能继续执行。

-(void)addDependency:(NSOperation *)op; //添加任务依赖

-(void)removeDependency:(NSOperation *)op; // 移除任务依赖

@property (readonly, copy) NSArray<NSOperation *> *dependencies;// 获取任务依赖

- (void)addOperation {
    //创建一个NSBlockOperation对象,传入一个block
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            sleep(2);
            NSLog(@"operation - Task1 i = %d, %@ ",i, [NSThread currentThread]);
        }
       
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(2);
        NSLog(@"operation2 - currentThread = %@ ",[NSThread currentThread]);

    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(2);
        NSLog(@"operation3 - currentThread = %@ ",[NSThread currentThread]);

    }];
 
    // operation 依赖于operation2、operation3
    // 所以会先执行 operation2、operation3 再执行operation
    [operation addDependency:operation3];
    [operation addDependency:operation2];

    NSLog(@"operation.dependencies = %@", [operation dependencies]);
    NSOperationQueue *queues = [[NSOperationQueue alloc] init];
    [queues addOperations:@[operation,operation2,operation3] waitUntilFinished:NO];
}

输出:
operation.dependencies = (
    "<NSBlockOperation: 0x7feb226063a0>",
    "<NSBlockOperation: 0x7feb22606290>"
)
operation3 - currentThread = <NSThread: 0x600001232cc0>{number = 3, name = (null)}
operation2 - currentThread = <NSThread: 0x600001220ec0>{number = 5, name = (null)}
operation - Task1 i = 0, <NSThread: 0x600001220580>{number = 6, name = (null)}
operation - Task1 i = 1, <NSThread: 0x600001220580>{number = 6, name = (null)}

注意:
1、要在把任务添加到NSOperationQueue之前添加任务依赖
2、切记循环依赖,会产生死循环。
3、被依赖的任务确保都添加到NSOperationQueue,可以是不同的queue

优先级

queuePriority属性,决定了任务在队列中执行的顺序。
queuePriority属性只对同一个队列中的任务有效。
queuePriority属性不能取代依赖关系。
对于进入准备就绪状态的任务优先级高的任务优先于优先级低的任务。
优先级高的任务不一定会先执行,因为已经进入准备就绪状态的任务即使是优先级低也会先执行。

新创建的operation对象的优先级默认是NSOperationQueuePriorityNormal,可以通过setQueuePriority:方法来修改优先级。

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};

- (void)addPriorityOperation {
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            sleep(2);
            NSLog(@"operation - Task1 i = %d, %@ ",i, [NSThread currentThread]);
        }
       
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(2);
        NSLog(@"operation2 - currentThread = %@ ",[NSThread currentThread]);

    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(2);
        NSLog(@"operation3 - currentThread = %@ ",[NSThread currentThread]);

    }];
    operation3.queuePriority = NSOperationQueuePriorityHigh;
    
    NSOperationQueue *queues = [[NSOperationQueue alloc] init];
    [queues addOperations:@[operation,operation3,operation2] waitUntilFinished:NO];
  
}
输出:
operation3 - currentThread = <NSThread: 0x600002276ec0>{number = 3, name = (null)}
operation2 - currentThread = <NSThread: 0x600002275b00>{number = 8, name = (null)}
operation - Task1 i = 0, <NSThread: 0x600002268200>{number = 4, name = (null)}
operation - Task1 i = 1, <NSThread: 0x600002268200>{number = 4, name = (null)}

可以发现operation3优先级最高,会第一个被执行。

但是如果添加[operation3 addDependency:operation];,虽然operation3优先级最高,但是它依赖于operation执行完才能执行。

输出:
operation - Task1 i = 0, <NSThread: 0x600001638780>{number = 7, name = (null)}
operation2 - currentThread = <NSThread: 0x60000165c800>{number = 6, name = (null)}
operation - Task1 i = 1, <NSThread: 0x600001638780>{number = 7, name = (null)}
operation3 - currentThread = <NSThread: 0x60000165c800>{number = 6, name = (null)}

会先执行operation后,再执行operation3

线程间通信

- (void)invocationOperationAction:(NSString *)message {
    NSLog(@"message = %@, currentThread = %@",message,[NSThread currentThread]);
    [[NSOperationQueue mainQueue]addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            sleep(2);
            NSLog(@"mainQueue Task1 i = %d, %@",i, [NSThread currentThread]);
        }
    }];
    
}
- (void)addOperation {
    //创建一个NSBlockOperation对象,传入一个block
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            sleep(2);
            NSLog(@"operation - Task1 i = %d, %@ ",i, [NSThread currentThread]);
        }
       
    }];

    /*
    创建一个NSInvocationOperation对象,指定执行的对象和方法
    该方法可以接收一个参数即object
    */
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationAction:) object:@"Hello, World!"];
   
    NSOperationQueue *queues = [[NSOperationQueue alloc] init];
    //设置最大并发数,如果设置为1则串行执行
    [queues setMaxConcurrentOperationCount:2];
    [queues addOperations:@[operation,invocationOperation] waitUntilFinished:NO];
}

输出:
message = Hello, World!, currentThread = <NSThread: 0x600000cf6680>{number = 7, name = (null)}
operation - Task1 i = 0, <NSThread: 0x600000cbb200>{number = 6, name = (null)}
mainQueue Task1 i = 0, <_NSMainThread: 0x600000cf0440>{number = 1, name = main}
operation - Task1 i = 1, <NSThread: 0x600000cbb200>{number = 6, name = (null)}
mainQueue Task1 i = 1, <_NSMainThread: 0x600000cf0440>{number = 1, name = main}
operation - Task1 i = 2, <NSThread: 0x600000cbb200>{number = 6, name = (null)}

其他

NSOperation包含了一个状态机来描述每一个操作的执行。
isReady → isExecuting → isFinished。
为了替代不那么清晰的state属性,状态直接由上面那些keypath的KVO通知决定
也就是说,当一个操作在准备好被执行的时候,它发送了一个KVO通知给isReady的keypath,让这个keypath对应的属性isReady在被访问的时候返回YES。
每一个属性对于其他的属性必须是互相独立不同的,也就是同时只可能有一个属性返回YES,从而才能维护一个连续的状态:

isReady: 返回YES表示操作已经准备好被执行, 如果返回NO则说明还有其他没有先前的相关步骤没有完成。

isExecuting: 返回YES表示操作正在执行,反之则没在执行。

isFinished : 返回YES表示操作执行成功或者被取消了,NSOperationQueue只有当它管理的所有操作的isFinished属性全标为YES以后操作才停止出列,也就是队列停止运行,所以正确实现这个方法对于避免死锁很关键。

// 可取消操作,实质是标记 isCancelled 状态。 判断操作状态方法
- (void)cancel; 
- (BOOL)isCancelled; 
// 判断操作是否已经结束。
- (BOOL)isFinished; 
//判断操作是否正在在运行。
- (BOOL)isExecuting; 
// 判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关
- (BOOL)isReady;

//阻塞当前线程,直到该操作结束。可用于线程执行顺序的同步。
- (void)waitUntilFinished; 
// 阻塞当前线程,直到队列中的操作全部执行完毕。
- (void)waitUntilAllOperationsAreFinished; 
// 可以取消队列的所有操作
- (void)cancelAllOperations;

// completionBlock会在当前操作执行完毕时执行 completionBlock。
- (void)setCompletionBlock:(void (^)(void))block;  

// 队列是否处于暂停状态。 YES 为暂停状态,NO 为恢复状态
@property (getter=isSuspended) BOOL suspended;

// 当前队列中的操作数。
- (NSUInteger)operationCount;  
// 获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil。
+ (id)currentQueue; 

多线程应用

1、多个网络请求完成后执行下一步

第一个实现方案:enter/leave group

- (void)mutTaskToOneEnterLeaveGroup {
    
    NSString *str = @"https://www.baidu.com";
    NSURL *url = [NSURL URLWithString:str];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];

    dispatch_group_t downloadGroup = dispatch_group_create();
    for (int i=0; i<5; i++) {
        dispatch_group_enter(downloadGroup);
        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"end---%d,current = %@",i, [NSThread currentThread]);
            dispatch_group_leave(downloadGroup);
        }];
        [task resume];
    }
    dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
        NSLog(@"end");
    });
}
输出结果:
end---0,current = <NSThread: 0x600000ae12c0>{number = 3, name = (null)}
end---1,current = <NSThread: 0x600000a87d00>{number = 6, name = (null)}
end---4,current = <NSThread: 0x600000ac9f40>{number = 7, name = (null)}
end---2,current = <NSThread: 0x600000ae4580>{number = 4, name = (null)}
end---3,current = <NSThread: 0x600000ac9f40>{number = 7, name = (null)}
end

第二个实现方案:信号量

- (void)mutTaskToOneSemaphore {
    NSString *str = @"https://www.baidu.com";
    NSURL *url = [NSURL URLWithString:str];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];

    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    __block int count = 1;
    for (int i=0; i<5; i++) {

        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"end---%d,current = %@",i, [NSThread currentThread]);
            count++;
            if (count==5) {
                dispatch_semaphore_signal(sem);
                count = 0;
            }
        }];
        [task resume];
    }
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"end");
    });
}

输出:
end---0,current = <NSThread: 0x600000574a00>{number = 6, name = (null)}
end---1,current = <NSThread: 0x600000538240>{number = 5, name = (null)}
end---3,current = <NSThread: 0x600000538240>{number = 5, name = (null)}
end---2,current = <NSThread: 0x6000005629c0>{number = 3, name = (null)}
end---4,current = <NSThread: 0x6000005629c0>{number = 3, name = (null)}
end

1、semaphore 初始创建时计数为 0。
2、进入for循环内异步执行 不做等待,继续向下执行
3、执行dispatch_semaphore_wait,semaphore 减 1 此时semaphore -1,当前线程进入等待状态。
4、然后for循环内,当执行到dispatch_semaphore_signal方法,semaphore 加1, 此时 semaphore 0,正在被阻塞的线程(主线程)恢复继续执行。
5、最后结束到end

2、多个网络请求顺序执行后执行下一步

实现方案:信号量

dispatch_semaphore_wait- (void)synMutTasksSemaphore {
    NSString *str = @"https://www.baidu.com";
    NSURL *url = [NSURL URLWithString:str];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];

    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    for (int i=0; i<5; i++) {

        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"end---%d,current = %@",i, [NSThread currentThread]);
            dispatch_semaphore_signal(sem);

        }];
        [task resume];
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    }

    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"end");
    });
    
}

输出:
end---0,current = <NSThread: 0x600003894780>{number = 7, name = (null)}
end---1,current = <NSThread: 0x6000038f8940>{number = 5, name = (null)}
end---2,current = <NSThread: 0x6000038a1b40>{number = 8, name = (null)}
end---3,current = <NSThread: 0x6000038b5300>{number = 6, name = (null)}
end---4,current = <NSThread: 0x6000038f8940>{number = 5, name = (null)}
end

1、semaphore 初始创建时计数为 0。
2、进入for循环内异步执行 不做等待,继续向下执行
3、每次遍历都执行dispatch_semaphore_wait,semaphore 减 1 此时semaphore -1,当前线程进入等待状态。
4、每次遍历的当前任务结束时,当执行到dispatch_semaphore_signal方法,semaphore 加1, 此时 semaphore 0,正在被阻塞的线程(主线程)恢复继续执行。
5、for循环结束后,在进行最后结束到end

3、异步操作两组数据时, 执行完第一组之后, 才能执行第二组

可参考上面dispatch_barrier_async的使用。

4、dispatch_once 的使用场景

在单例中使用,一个类仅有一个实例且提供一个全局访问点

+ (instancetype)shared {
    static HFCustomModel *object;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        object = [[HFCustomModel alloc] init];
    });
    return object;
}

在method-Swizzling使用保证方法只交换一次。

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(hf_viewWillAppear:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod = class_addMethod(class,
                                            originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

5、综合运用一

下面的代码会报错吗?

int a = 0;
while (a < 5) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        a++; 
    });
}

会。

这个涉及到block的变量捕获,此处需要__block修饰才可。

点击 block知识小结了解更多block相关。

** 下面代码会输出什么?**

__block int a = 0;
while (a < 5) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        a++;
    });
}
NSLog(@"a = %d",a);

输出结果大于等于5。

原因:
刚进入while循环时,a=0,然后进行a++
因为while内是异步并发会开辟子线程
当线程2在a=0执行a++时,线程3有可能已经完成了a++使a=1。
由于是操作同一片内存空间,线程3修改了a导致线程2中a的值也发生了变化。
慢一点点的线程2对已经是a=1进行a++操作。
同理还有线程4、线程5、线程n的存在。
可以这么理解,线程2、3、4、5、6同时在a=0时操作a
线程2、3、4、5按顺序完成了操作,此时a=4
然后线程6开始操作了,但是它还没执行完就跳到了下一次循环了开辟了线程7开始a++
当线程6执行结束修改a=5之后来到while条件判断就会跳出循环
然而I/O输出比较耗时,此时线程7又刚好完成了再打印,就会输出大于5

也有那么种理想情况,异步并发都比较听话,刚好在a=5时没有子线程
此时就会输出5。

可以在while循环内添加一行输出,进行验证

NSLog(@"a = %d———currentThread = %@", a, [NSThread currentThread]);

while外面的打印已经执行,但是子线程还是有可能在对a进行操作的。

怎么解?

1、同步函数替换异步函数
可以满足需求,但是效果不是很好,效率太低。
2、信号量
可以满足需求,并且效率高。

__block int a = 0;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

while (a < 5) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"a = %d———currentThread = %@", a, [NSThread currentThread]);
        a++;
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

}
NSLog(@"end = a = %d",a);

输出:
a = 0———currentThread = <NSThread: 0x6000038ac600>{number = 7, name = (null)}
a = 1———currentThread = <NSThread: 0x6000038ac600>{number = 7, name = (null)}
a = 2———currentThread = <NSThread: 0x6000038ac600>{number = 7, name = (null)}
a = 3———currentThread = <NSThread: 0x6000038ac600>{number = 7, name = (null)}
a = 4———currentThread = <NSThread: 0x6000038ac600>{number = 7, name = (null)}
end = a = 5

信号量可以实现的原因和上面实现多个请求顺序执行的原因相似。

6、综合运用二

下面代码会输出什么?

dispatch_queue_t queue = dispatch_queue_create("com.concurrent", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *marr = @[].mutableCopy;
for (int i = 0; i < 1000; i++) {
    dispatch_async(queue, ^{
        [marr addObject:@(i)];
    });
}
NSLog(@"%lu", marr.count);

会崩溃。

多个线程同时对NSMutableArray进行写操作,而NSMutableArray不是线程安全的。

怎么解决?

1、同步函数替换异步函数
可以满足需求,但是效果不是很好,效率太低。
2、for循环内 @synchronized锁+dispatch_barrier_async打印输出
3、for循环内栅栏函数+dispatch_async打印输出(和栅栏函数在同一个队列中)

dispatch_queue_t queue = dispatch_queue_create("com.concurrent", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *marr = @[].mutableCopy;

for (int i = 0; i < 10000; i++) {
    dispatch_async(queue, ^{
        @synchronized (self) {
           [marr addObject:@(i)];
       }
    });
}
// 在所有任务执行完成后再输出数组的 count
dispatch_barrier_async(queue, ^{
    NSLog(@"%lu", (unsigned long)marr.count);
});
dispatch_queue_t queue = dispatch_queue_create("com.concurrent", DISPATCH_QUEUE_CONCURRENT);
NSMutableArray *marr = @[].mutableCopy;

for (int i = 0; i < 10000; i++) {
    dispatch_async(queue, ^{
        [marr addObject:@(i)];
    });
    dispatch_barrier_async(queue, ^{});

}
dispatch_async(queue, ^{
    NSLog(@"%lu", marr.count);
});

相关链接
OperationObjects
SDWebImage

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,692评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,482评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,995评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,223评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,245评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,208评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,091评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,929评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,346评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,570评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,739评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,437评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,037评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,677评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,833评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,760评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,647评论 2 354

推荐阅读更多精彩内容