首先我们要搞清楚一些概念:
主线程是在可以其他队列的任务进行操作的
主队列mainqueue的任务肯定在是主线程上操作的
对于串行队列,每创建一个串行队列,系统就会对应创建一个线程,同时这些线程都是并行执行的,只是在串行队列中的任务是串行执行的。大量的创建串行队列会导致大量消耗内存,这是不可取的做法。串行队列的优势在于他是一个线程,所以在操作一个全局数据时候是线程安全的。当想并行执行而不发生数据竞争时候可以用并行队列操作
如果我们cpu是单核,能不能并行操作任务:不能
如果我们cpu是双核,能不能并行操作任务:能 cpu1 处理A, cpu2 处理B
串行队列,并行队列:
我们想象成这样的场景,银行有一个窗口
串行队列就是需要排队按顺序办理,在前面的就先办理,后面的就后办理,不能插队
并行队列就是他们没有排队,完全是靠挤的,谁先办理也就是随机的
串发和并发:
我们想象成这样的场景,串发就是只有当前的窗口(即当前线程,可以是主线程也可以是子线程)可以办理,不会再开新的窗口
并发就是会再给你开多个窗口(即多个线程)办理。
串行和串发:没有开启新线程,任务是逐个完成的
串行和并发:开启新线程,任务是逐个完成的
并行和串发:没有开启新线程,任务是逐个执行
并行和并发:开启新线程,任务是并发的
串行队列:
dispatch_queue_t eoc_queueOneTT = dispatch_queue_create("eoc_queueOneTT", DISPATCH_QUEUE_SERIAL);
打印结果:
宽度是:0x1,因为是串行,所以宽度是1,每次只允许一个任务进行。
并行队列:
dispatch_queue_t eoc_queueOneTT = dispatch_queue_create("eoc_queueOneTT", DISPATCH_QUEUE_CONCURRENT);
打印结果:
宽度是:0xffe,因为是并行,所以宽度不是1,每次允许多个任务进行。
串行队列(串发即同步,并发即异步)
// 串行队列 只能开启一个任务 (串发,即同步 dispatch_sync 在当前线程操作 sync:同步)
- (void)serialQueue{
// 创建用户串行队列 serial
dispatch_queue_t queue = dispatch_queue_create("serQueue", DISPATCH_QUEUE_SERIAL);
// 异步
dispatch_async(queue, ^{
NSLog(@"1:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3:%@", [NSThread currentThread]);
});
// 同步
dispatch_sync(queue, ^{
NSLog(@"4:%@", [NSThread currentThread]);
});
}
输出结果:
串行队列就后面参数是:DISPATCH_QUEUE_SERIAL的时候。
同步和异步只是两种方法的不同,dispatch_async异步的时候,会开启新的线程。dispatch_sync同步的时候,就在当前线程,即主线程。而且都是按照顺序执行的。
并行队列(串发即同步,并发即异步)
// 并行队列 同时可以开启多个任务 (并发,即异步 dispatch_async 另外再开新的线程 async:异步)
- (void)conCurrentQueue{
// 创建用户串行队列 concurrent
dispatch_queue_t queue = dispatch_queue_create("serQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"1:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3:%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"4当前线程:%@", [NSThread currentThread]);
});
}
输出结果:
并行队列就后面参数是:DISPATCH_QUEUE_CONCURRENT的时候。
同步和异步只是两种方法的不同,dispatch_async异步的时候,会开启新的线程。dispatch_sync同步的时候,就在当前线程,即主线程。但是都没有按照顺序执行。
组(用于多个任务执行)
- (void)group_gcd{
// 1 创建组
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("serQueue", DISPATCH_QUEUE_CONCURRENT);
// 2向组添加任务 任务数count=3
dispatch_group_async(group, queue, ^{
NSLog(@"task one::%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"task two::%@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"task three::%@", [NSThread currentThread]);
});
// 3组任务全部完成了,就通知, 然后执行下面block里面的方法 任务数count为0
dispatch_group_notify(group, queue, ^{
NSLog(@"finish all task");
});
}
输出结果:
不管前面是串行还是并行队列,只有组里面的添加的任务数做完了,当任务数为0的时候,才会走最后一个组通知的方法,执行block里面的方法。
组的应用
一个界面有多个请求的时候,可以用到组。当第二个网络请求里面可能用到第一个请求成功里面的数据,就要等第一个请求成功过后,才能执行第二个请求,类似这样的情况。
// 一个界面执行加载多个网络请求,可以用到group
- (void)groupStyleTwo{
// 1 创建组
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("serQueue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 3; i++) {
// 填加一个空任务,让任务数+1,当任务完成后,再移除空任务,让任务数-1,最后任务数为0的时候,才会走通知的方法
// 或者用最下面注释的那三行写法,效果也是一样的
dispatch_group_enter(group); // 任务数+1
dispatch_async(queue, ^{
[self netLoadSync:i]; // 如果在这个任务再开一个线程,那么不能保证你的需求
dispatch_group_leave(group);// 任务数-1
});
// dispatch_group_async(group, queue, ^{
// [self netLoadSync:i];
// });
}
// 3组任务全部完成了,就通知
dispatch_group_notify(group, queue, ^{
NSLog(@"finish all task");
});
}
// task 同步
- (void)netLoadSync:(int)taskCount
{
NSString *urlPath = @"http://svr.tuliu.com/center/front/app/util/updateVersions";
NSString *urlstr = [NSString stringWithFormat:@"%@?versions_id=1&system_type=1", urlPath];
NSURL *url = [NSURL URLWithString:urlstr];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
// 创建信号量,做标记,当请求成功并完成过后,才执行后面的代码
dispatch_semaphore_t sema = dispatch_semaphore_create(0); // 信号量 - 1
NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error){
// NSDictionary *infoDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
NSLog(@"完成了,taskcount:%d", taskCount);
// 完成过后,发生信号量
dispatch_semaphore_signal(sema); // 信号量 - 2
}];
[task resume];
// 阻塞代码,达到同步的效果,直到收到信号量(收到信号量,也就是说请求完成了),才继续执行
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); // 信号量 - 3
NSLog(@"finish 代码跑完了:%d",taskCount);
}
输出结果:
我们为了达到同步的效果,使用了信号量(代码中关于信号量的那三行代码),即只有请求成功过后,才会执行请求后面的代码,以免出现一些错误,后面要跟上一堆判断请求是否成功。
如果不加那三行信号量的代码,输出结果如下:
栅栏
- (void)barrier_fct{
dispatch_queue_t queue = dispatch_queue_create("serQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"分界线前:taskOne");
});
dispatch_async(queue, ^{
NSLog(@"分界线前:taskTwo");
});
dispatch_async(queue, ^{
NSLog(@"分界线前:taskThree");
});
dispatch_barrier_async(queue, ^{ // 分界线里面,queue可以看作是串行的,当前只能执行barrier里面的task
NSLog(@"分界线里面的任务");
});
dispatch_async(queue, ^{
NSLog(@"分界线后:taskFour");
});
dispatch_async(queue, ^{
NSLog(@"分界线后:taskFive");
});
// 可以看成三个部分,分界线前面三个是一个部分,分界线里面是第二个部分,分界线后面两个是第三个部分。而且必须是第一部分执行完了,才能执行第二部分,第二部分执行完了,才能执行第三部分。
}
输出结果:
栅栏的应用
读写数组 mutableAry,如果开几个线程来操作数组
1 写数组(移除index=0对象), 2 写数组(移除了index=0的对象) // 有问题
1 读数组 2 读数组 // 没问题
1 读数组 2写数组
只要涉及到写操作(要做保护),当数组写的时候,不允许其他线程对它有操作,不然就会有问题。
- (void)viewDidLoad {
[super viewDidLoad];
_safeAry = [NSMutableArray array];
[_safeAry addObject:@"0"];
[_safeAry addObject:@"1"];
[_safeAry addObject:@"2"];
[_safeAry addObject:@"3"];
rwQueue = dispatch_queue_create("serQueue", DISPATCH_QUEUE_CONCURRENT);
}
- (void)testRWAry{
dispatch_queue_t queue = dispatch_queue_create("testRWAry", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 20; i++) {
// 读
dispatch_async(queue, ^{
NSLog(@"%d::%@", i, [self indexTo:i]);
});
// 写
dispatch_async(queue, ^{
[self addObject:[NSString stringWithFormat:@"%d", i+4]];
});
}
}
// 写 保证只有一个在操作,所以是把这个操作放在栅栏里面,而不是它的前面或者后面,因为当执行栅栏里面操作的时候,就只有它在执行(避免了同时多个写操作导致的问题)
- (void)addObject:(NSString*)object{
dispatch_barrier_async(rwQueue, ^{
if (object != nil) {
[_safeAry addObject:object];
}
});
}
// 主队列 mainqueue --> 主线程 mainThread
// 注意同步dispatch_sync,因为业务关系,必须马上数据,所以不能异步
- (NSString*)indexTo:(NSInteger)index{
__block NSString *result = nil;
dispatch_sync(rwQueue, ^{
if (index < _safeAry.count) {
result = _safeAry[index];
}
});
return result;
}
输出结果:
重复 执行任务
- (void)apply_gcd{
dispatch_queue_t queue = dispatch_queue_create("testRWAry", DISPATCH_QUEUE_CONCURRENT);
// 第一个参数是重复的次数
dispatch_apply(5, queue , ^(size_t count) {
NSLog(@"%zu", count);
});
}
输出结果:
延后 执行任务
- (void)after_GCD{
dispatch_queue_t queue = dispatch_queue_create("testRWAry", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"start:%@", [NSThread currentThread]);
// 又重新开启了一个线程来执行延后的操作,所以不需要开启RunLoop
dispatch_after(1, queue, ^{
NSLog(@"dispatch_after:%@", [NSThread currentThread]);
});
NSLog(@"end");
});
}
//只能延后执行,不能设置延后多少秒执行
输出结果:
激活
注意一开始创建队列,要创建成INACTIVE的
- (void)queueInactive{
// INACTIVE 未激活的,添加任务后不会自动执行,需要我们手动激活过后才会执行
dispatch_queue_t queue = dispatch_queue_create("testRWAry", DISPATCH_QUEUE_CONCURRENT_INACTIVE);
dispatch_async(queue, ^{
NSLog(@"start:%@", [NSThread currentThread]);
});
// 激活
dispatch_activate(queue);
}
传函数指针 _f IMP
void testFunction(){
NSLog(@"testFunction::-->%@", [NSThread currentThread]);
}
// _f IMP
- (void)function_f{
dispatch_queue_t queue = dispatch_queue_create("testRWAry", DISPATCH_QUEUE_CONCURRENT);
// 第三个参数是需要传函数指针
dispatch_async_f(queue, nil, testFunction);
}
GCD定时器
@implementation GCDTwoViewVC
- (void)viewDidLoad {
[super viewDidLoad];
queue = dispatch_queue_create("eoc_queue", DISPATCH_QUEUE_CONCURRENT);
[self timeSource];
}
// 定时器 unix (pthread) GCD()
dispatch_source_t soure;
- (void)timeSource{
// source关联到队列
soure = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 配置soure的时间
/*
GCD的时间参数,一般是纳秒(1秒 == 10的9次方纳秒) 所以要乘上 NSEC_PER_SEC
第二个参数:马上触发
第三个参数:间隔一秒
第四个参数:误差一秒
*/
dispatch_source_set_timer(soure, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 1 * NSEC_PER_SEC);
// 配置source的处理事件
dispatch_source_set_event_handler(soure, ^{
NSLog(@"soure_event:==%@", [NSThread currentThread]);
});
// 开启定时器
dispatch_resume(soure);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 暂停定时器
// dispatch_suspend(soure);
// 取消定时器
dispatch_cancel(soure);
}
@end
信号量 + 互斥锁 -> 造成死锁
在互斥锁里面使用了信号量,容易造成死锁的情况,当信号量阻塞了过后,不能解锁,也就进行不到下一步。
#import "SignalLock.h"
#import <pthread.h>
@interface SignalLock (){
NSLock *mutexLock;
dispatch_semaphore_t sema;
int count;
}
@end
@implementation SignalLock
- (instancetype)init{
self = [super init];
if (self) {
mutexLock = [NSLock new];
sema = dispatch_semaphore_create(0);
}
return self;
}
- (void)signalLock{
[NSThread detachNewThreadSelector:@selector(threadOne) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:@selector(threadTwo) toTarget:self withObject:nil];
}
- (void)threadOne{
[self signalLockWrite];
}
- (void)threadTwo{
[self signalLockRead];
}
#pragma mark - 信号量+ 互斥锁(死锁)
///////// 信号量 + 互斥锁(死锁)
- (void)signalLockWrite{
while (1) {
// 上锁
[mutexLock lock];
if (count >= 10) {
// 没有内存了
NSLog(@"空间满了");
// 信号量阻塞,等待
// 因为这边已经上锁了,还没有解锁,所以你这里阻塞了过后,不能走到下面方法的dispatch_semaphore_signal,释放空间
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); // 阻塞
}else{
count++;
NSLog(@"空间:%d", count);
}
// 解锁
[mutexLock unlock];
}
}
- (void)signalLockRead{
while (1) {
// 上锁
[mutexLock lock];
if (count >= 10) {
count--;
NSLog(@"释放空间");
// 当上面上了锁,并用信号量阻塞了过后,没有解锁,就走不到这个方法来
dispatch_semaphore_signal(sema);
}else{
count++;
}
// 解锁
[mutexLock unlock];
}
}
@end
打印如下:
空间满了过后,不能走到下一步的释放空间。
条件量
为了解决上面的死锁问题可以用条件量来解决,条件量有两种OC的条件量和C的条件量,其中C的条件量要和C的互斥锁结合起来使用。
#import "C_ConditionLock.h"
#import <pthread.h>
@interface C_ConditionLock(){
// OC的条件量
NSCondition *_condition;
int count;
// C的互斥锁
pthread_mutex_t mutex; // NSLock OC的互斥锁
// C的条件量
pthread_cond_t cond;
}
@end;
@implementation C_ConditionLock
- (instancetype)init
{
self = [super init];
if (self) {
//初始化OC的条件量
_condition = [NSCondition new];
count = 0;
//初始化C的互斥锁和条件量
pthread_mutex_init(&mutex, 0);
pthread_cond_init(&cond, 0);
}
return self;
}
- (void)signalLock{
[NSThread detachNewThreadSelector:@selector(threadOne) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:@selector(threadTwo) toTarget:self withObject:nil];
}
- (void)conditionLockTheory{
[NSThread detachNewThreadSelector:@selector(conditionLockOne) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:@selector(conditionLockTwo) toTarget:self withObject:nil];
}
- (void)threadOne{
[self signalLockWrite];
}
- (void)threadTwo{
[self signalLockRead];
}
#pragma mark - OC条件量
- (void)signalLockWrite{
while (1) {
[_condition lock];
if (count >= 10) {
// 没有内存了
NSLog(@"空间满了");
[_condition wait];
}else{
count++;
}
[_condition unlock];
}
}
- (void)signalLockRead{
while (1) {
[_condition lock];
if (count >= 10) {
count--;
NSLog(@"释放空间");
[_condition signal];
}else{
count++;
}
[_condition unlock];
}
}
#pragma mark - OC条件量, C的实现
- (void)conditionLockOne{
while (1) {
// 上锁
pthread_mutex_lock(&mutex);
if (count >= 10) {
// 没有内存了
NSLog(@"空间满了");
// 阻塞,等待
pthread_cond_wait(&cond, &mutex);// 阻塞 但是互斥锁(mutex)这个解了,即可以进行下面的方法了,没有被锁住了
}else{
count++;
}
// 解锁
pthread_mutex_unlock(&mutex);
}
}
- (void)conditionLockTwo
{
while (1) {
pthread_mutex_lock(&mutex);
if (count >= 10) {
count--;
NSLog(@"释放空间");
// 发送信号
pthread_cond_signal(&cond);
}else{
count++;
}
pthread_mutex_unlock(&mutex);
}
}
@end
打印结果:
这样当空间满了过后,就能够释放空间,不会造成死锁
递归锁
递归锁也有OC和C两种实现
#import "RecursiveLock.h"
#import <pthread.h>
/*
OC的锁基于c的封装,锁对象化了
*/
@implementation RecursiveLock{
}
- (instancetype)init{
self = [super init];
if (self) {
[self oc_recursiveLockinit];
[self c_recursiveLockinit];
}
return self;
}
- (void)oc_recursiveLockinit{
// OC递归锁的初始化
recursiveLock = [[NSRecursiveLock alloc] init];
}
- (void)c_recursiveLockinit{
// C递归锁的初始化
pthread_mutexattr_t attr;
pthread_mutexattr_init (&attr);
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init (&_reclock, &attr);
pthread_mutexattr_destroy (&attr);
}
// 递归锁 OC
- (void)recursiveLock{
NSLog(@"start");
NSLog(@"result:%d", [self addCount:10]);
NSLog(@"end");
}
// 递归锁 C
- (void)recursiveLockTheory{
NSLog(@"theory start");
NSLog(@"theory result:%d", [self addC__Count:10]);
NSLog(@"theory end");
}
// 递归里面做安全策略,用互斥锁会造成死锁的
// 递归锁里可以累加锁的数量,只要最后加锁和解锁数量是一样的,就不会造成死锁
- (int)addCount:(int)count{
// 互斥锁,下面这行代码连续执行两次就会造成死锁
[recursiveLock lock];
if (count < 1) {
return count;
}
__block int tmp;
NSLog(@"count::%d", count);
tmp = count + [self addCount:count-1];
[recursiveLock unlock];
return tmp;
}
// C 里面递归锁的实现
- (int)addC__Count:(int)count{
pthread_mutex_lock(&_reclock);
if (count < 1) {
return count;
}
__block int tmp;
NSLog(@"theory count::%d", count);
tmp = count + [self addC__Count:count-1];
pthread_mutex_unlock(&_reclock);
return tmp;
}
@end
OC的锁基于C的封装,只是将锁对象化了。
参考文章:
iOS主线程和主队列的区别
IOS GCD线程相关内容(dispatch_sync,dispatch_async)
GCD 学习(二)dispatch_queue_create创建Dispatch Queue