iOS多线程、线程安全和线程间通信

 

Posted by     DH on August 9, 2017

多线程的概念

一个运行着的程序叫做一个进程,一个进程至少包含一个线程,线程是程序的执行流。在iOS中程序一旦启动就有一个线程一直运行,这个线程叫主线程。主线程是其他所 有线程的父线程。

系统中的所有县城共享进程的地址空间,所以能够自由的访问进程的空间变量。多线程访问的变量称之为“共享变量”。共享变量主要是“全局变量”和“静态变量”。

各个线程都有自己的栈,线程不能访问其他线程的的栈内变量。

进程是操作系统分配资源和调度的基本单位,线程是进程基本执行单位,一个进程对应多个线程。

线程又分为主线程和非主线程:

一般来说:主线程用于处理UI,所有的UI操作都要在主线程进行,耗时操作放在非主线程。

其实,CPU在一个单位时间只能处理一个线程(单CPU),但是可以通过快速的切换线程,就像同时在处理多个线程一样。

网上有一个比喻:

进程就是一列火车,线程是车厢,一列火车至少要有一节车厢,就如一个进程至少要有一个线程,车厢离开火车就无法运行,多个车厢可以提高运行效率。

多线程的目的就是提高系统的运行效率,将耗时操作都放在后台。

互斥

在多线程的环境下,不同的线程可以同时操作某个资源,例如某些线程在读取某个数,而另外一些线程在写这个数,这个时候就很容易产生难以预料的结果,使得线程不安全,解决这个问题的方式就是让线程的操作互斥,主要使用互斥锁。

NSLock

NSLock是基于信号量的,他只允许单个的线程获得并使用。

使用方法:

-(void)addNumber:(int)number{
    // 初始化一个锁
    NSLock *aLock = [[NSLock alloc]init];
    
    // 加锁
    [aLock lock];
    
    
    number ++; // 临界区
    
    // 解锁
    [aLock unlock];
}
		

NSConditionLock

条件锁持有整形数值,根据这个值获得锁或者等待。

-(void)addNumber2:(int)number{
    // 初始化一个锁
    NSConditionLock *conditonLock = [[NSConditionLock alloc]initWithCondition:1];
    
    // 加锁
    [conditonLock lockWhenCondition:1];
    
    
    number ++; // 临界区
    
   
    // 解锁
    [conditonLock unlockWithCondition:0];
    
}
		

NSRecrusiveLock

递归锁。某线程得到锁之后,想要获得该锁的线程就会进入休眠,使用NSLock时,如果获得锁的线程在没有释放的情况下,又再次获得这个锁,就会造成死锁。例如:

 // 加锁
    [aLock lock];
    [aLock lock];
    
    
    number ++; // 临界区
    
    // 解锁
    [aLock unlock];
    [aLock unlock];		

使用递归锁,同一个线程多次获得同一个锁,也不会产生死锁,只要最后获得的次数和释放的次数一致,就会释放锁。

synchronized

程序块内的代码不能被多线程同时使用。

@synchronized (objct) {
        // 想要执行的代码
    }		

运行这一段代码时,运行时系统就创建排斥的执行该段代码的锁。

@synchronized(obj)指令使用的obj为该锁的唯一标识,只有当标识相同时,才为满足互斥, 如果线程2中的@synchronized(obj)改为@synchronized(self),那么线程2就不会被阻塞。

线程的状态、转换和生命周期

线程的生命周期包括:新建–就绪–运行–阻塞–死亡,他们之间的转化如下图所示:

新建:实例化对象

就绪:调用线程对象的start函数,线程被假如到可调度池,等待CPU调度

运行:CPU调度可调度池的线程。线程执行的过程中,可能会在就绪和运行状态之间切换。这些都是由CPU控制的。

阻塞:在满足某中条件的时候,正在运行的线程会从运行状态转化成阻塞状态,主要包括:sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥锁)

死亡:线程运行完,正常结束。第二种是满足某种条件的时候,线程内部终止。

四中多线程方案

包括pthread、NSthread、GCD、NSOperation

借用网友的一张图片进行概述:

多线程安全

在多线程环境下,如果多个线程同时对某个资源进行读写操作,可能会导致数据错乱,出现难以预料的结果。

解决这个问题的方式就是给线程加锁。

(1)互斥锁

前面已经说过了,包括NSLock、NSConditionLock、@synchronized.

(2) 自旋锁

加了自旋锁,当新线程当问加锁的代码时,如果发现该段代码已经加锁,就会一直的等待这段代码执行完毕,解锁。相当于一直尝试执行这段代码,很耗性能。

atomic和nonatomic

定义属性的时候经常用到。

noatomic:非原子属性,同时可被多个线程读写,线程不安全,效率高 atomic:原子性,线程安全,同时只能被一个线程访问。

被atomic修饰的属性,会自动的在set方法中调用@synchronized(self)。所以效率很低,因为其实@synchronized会将他修饰的代码块加入到一个队列中,而同一时间,线程只能执行队列中的一个代码块,所以效率很低。

NSThread

NSThread创建

(1)初始化方式,然后调用start函数

    // 创建一个线程,处于创建状态
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(test) object:nil];
    
    // 调用线程的start方法,线程处于可运行状态,被加入到可调度线程池等待CPU的调度。
    [thread start];		

    // 创建一个线程,处于创建状态
    NSThread *thread2 = [[NSThread alloc]initWithBlock:^{
        // 线程中要运行的代码
    }];
    // 调用线程的start方法,线程处于可运行状态,被加入到可调度线程池等待CPU的调度。
    [thread2 start];

(2)类方法创建线程

2.1 detachNewThreadSelector

这个类方法,直接创建好线程,并且将线程放到可调度池,线程自动启动。

    // 线程创建好之后,自动启动
    [NSThread detachNewThreadSelector:@selector(test) toTarget:self withObject:nil];
    
    [NSThread detachNewThreadWithBlock:^{
    // 线程创建好之后,自动启动
    }];

2.2 detachNewThreadSelector

这个类方法,直接创建好线程,并且将线程放到可调度池,线程自动启动。

    // 线程创建好之后,自动启动
    [NSThread detachNewThreadSelector:@selector(test) toTarget:self withObject:nil];
    
    [NSThread detachNewThreadWithBlock:^{
    // 线程创建好之后,自动启动
    }];


2.3 performSelectorInBackground

这个方法现在用的不多,隐式创建一个线程,直接启动

    // 隐式创建,现在用的不多,直接创建好就启动
    [self performSelectorInBackground:@selector(test) withObject:nil];		

NSThread类方法

(1)[NSThread exit]; 终止线程

(2)[NSThread currentThread];   返回当前线程的线程号和名字,线程号1表示主线程。

(3)[NSThread sleepForTimeInterval:10];   线程休眠10秒

(4)[NSThread sleepUntilDate:[NSData data]];   线程休眠到某个指定时间

(5)[NSThread isMainThread];   当前线程是不是主线程

(6)[NSThread isMultiThreaded];    当前情况是都存在多线程

(7)NSThread *mainThread = [NSThread mainThread];   取得主线程

NSThread属性

(1)thread.isExecuting;   线程是否在执行

(2)thread.isCancelled;   线程是否被取消

(3)thread.isFinished;   线程是否完成

(4)thread.isMainThread;   是否是主线程

(5)thread.threadPriority;   线程的优先级,取值范围0.0到1.0,默认优先级0.5,1.0表示最高优先级,优先级高,CPU调度的频率高

GCD

GCD是异步执行任务的技术之一,开发者只需要定义要执行的任务,然后追加这些任务到GCD的队列,GCD就能生成必要的线程执行相应的任务。

线程作为管理作为系统的一部分,深入内核,所以效率非常高。

GCD中的概念说明

(1)任务

任务就是程序员想要完成的操作或者功能,在GCD中,我们一般都将任务封装成一个block(也就是一个匿名函数,或者理解成代码段)。然后程序员自行决定添加到串行队列或者并行队列,程序员的工作就完成了,接着等待CPU的调度。

(2)同步(sync)和异步(asycn)

同步和异步决定了是否要新开线程。

同步:不开新的线程,任务一个执行完之后执行队列中的下一个任务;

异步:开启新的线程,多个任务可以在多个线程中一起执行;

(3)串行队列和并行队列

串行队列:我理解的是任务像珠子一样被穿在一起,只有一个一个的顺序执行

并行队列:队列中的任务会被多个线程同时执行

(4)主队列和全局队列

主队列:一个应用中只有一个主队列,是串行队列

全局队列:并行队列,系统分配,无需创建

我们在使用GCD的时候,只需要将任务添加到队列,然后指定任务执行的方式是同步还是异步,就OK了。

队列的创建

(1)并行队列

dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);		

(2)串行队列

dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);		

全局队列和主队列不能创建,只能取得。

(3)全局队列

全局队列的优先级包括: #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高优先级 #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)优先级 #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低优先级 #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台优先级

// 第一个参数是优先级
//iOS8开始使用服务质量,现在获取全局并发队列时,可以直接传0
dispatch_queue_t globleQueue = dispatch_get_global_queue(0, 0);		

(4)主队列

主队列一般只能用于更新UI

dispatch_queue_t mainQueue = dispatch_get_main_queue();		

同步和异步任务的创建

  • 同步的创建
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        // 任务
        NSLog(@"dslfhl");
    });	

  • 异步的创建
dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 任务
        NSLog(@"dslfhl");
    });	

GCD中同步/异步和并行/串行的组合

一共有四种队列和两种执行方式,可以进行组合:

  • 串行同步

只要有同步就不会产生新的线程,串行会一个一个顺序执行,因此串行同步不产生新线程,在当前线程中顺序执行。

例子如下:

    // 创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        NSLog(@"串行同步--任务1--线程是:%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"串行同步--任务2--线程是:%@",[NSThread currentThread]);
    });


输出结果是:

2017-08-16 17:08:59.912 TestNew[2972:1293325] 串行同步--任务1--线程是:<NSThread: 0x608000261dc0>{number = 1, name = main}
2017-08-16 17:08:59.912 TestNew[2972:1293325] 串行同步--任务2--线程是:<NSThread: 0x608000261dc0>{number = 1, name = main}
2017-08-16 17:08:59.912 TestNew[2972:1293325] 串行同步--任务3--线程是:<NSThread: 0x608000261dc0>{number = 1, name = main}

观察结果可以发现,虽然我们创建了一个线程,但是最后还是使用的主线程,没有创建新的线程,并且顺序执行。

  • 串行异步

串行队列中的任务只能一个一个执行,而异步可以开启多个线程。所以串行异步的结果是开启新的线程,任务顺序执行

 // 创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
        NSLog(@"串行异步--任务1--线程是:%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"串行异步--任务2--线程是:%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"串行异步--任务3--线程是:%@",[NSThread currentThread]);
    });

输出结果:

2017-08-16 17:15:49.653 TestNew[3051:1325440] 串行异步--任务1--线程是:<NSThread: 0x60800006ed80>{number = 3, name = (null)}
2017-08-16 17:15:49.654 TestNew[3051:1325440] 串行异步--任务2--线程是:<NSThread: 0x60800006ed80>{number = 3, name = (null)}
2017-08-16 17:15:49.654 TestNew[3051:1325440] 串行异步--任务3--线程是:<NSThread: 0x60800006ed80>{number = 3, name = (null)}

可以看到创建了一个number = 3的新线程,任务顺序执行。

  • 并行同步

同步表示不开启新的线程,因此执行结果是任务顺序执行,不开启新的线程

// 创建并行队列
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queue, ^{
        NSLog(@"并行同步--任务1--线程是:%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"并行同步--任务2--线程是:%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"并行同步--任务3--线程是:%@",[NSThread currentThread]);
    });
		

输出结果是:

2017-08-16 17:25:46.613 TestNew[3142:1360637] 并行同步--任务1--线程是:<NSThread: 0x60800006d280>{number = 1, name = main}
2017-08-16 17:25:46.613 TestNew[3142:1360637] 并行同步--任务2--线程是:<NSThread: 0x60800006d280>{number = 1, name = main}
2017-08-16 17:25:46.613 TestNew[3142:1360637] 并行同步--任务3--线程是:<NSThread: 0x60800006d280>{number = 1, name = main}

  • 并行异步

并行表示多个任务可以同时被执行,异步会开启新的线程,所以并行异步会开启多线程,并且异步执行

// 创建并行队列
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"并行异步--任务1--线程是:%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"并行异步--任务2--线程是:%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"并行异步--任务3--线程是:%@",[NSThread currentThread]);
    });

		

输出结果是:

2017-08-16 19:54:07.557 TestNew[3498:1450488] 并行异步--任务2--线程是:<NSThread: 0x60800007a100>{number = 4, name = (null)}
2017-08-16 19:54:07.557 TestNew[3498:1450485] 并行异步--任务3--线程是:<NSThread: 0x60000007dd00>{number = 5, name = (null)}
2017-08-16 19:54:07.557 TestNew[3498:1450517] 并行异步--任务1--线程是:<NSThread: 0x60000007e080>{number = 3, name = (null)}

可以看到开启了多个线程,任务执行顺序也不一定。

  • 主队列同步

主队列本来就是一个串行队列,而同步不开启新的线程,正好这个组合就会发生死锁,程序会崩溃

// 取得主队列(穿行队列)
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_sync(queue, ^{
        NSLog(@"主队列同步--任务1--线程是:%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"主队列同步--任务2--线程是:%@",[NSThread currentThread]);
    });
    
    dispatch_sync(queue, ^{
        NSLog(@"主队列同步--任务3--线程是:%@",[NSThread currentThread]);
    });

输出结果是:程序崩溃

为什么会崩溃呢?

搜了一下,网上说

// 取得主队列(穿行队列)
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_sync(queue, ^{
        NSLog(@"主队列同步--任务1--线程是:%@",[NSThread currentThread]);
    });
    

dispatch_sync这个函数正在主线程中执行,然后又在主队列中加入了block这个任务,dispatch_sync要等待block执行完再返回,而block又要等 dispatch_sync执行完,再顺序的执行,所以形成了死锁。

  • 主队列异步

主队列是串行队列,因此顺序执行,异步开启多线程,但是同一时间只能执行一个任务,因此任务在主队列中顺序执行。

这里不阻塞的原因是:asych函数添加了block就返回,不会等待block执行完再返回

    // 取得主队列(穿行队列)
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        NSLog(@"主队列同步--任务1--线程是:%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"主队列同步--任务2--线程是:%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"主队列同步--任务3--线程是:%@",[NSThread currentThread]);
    });

	

输出结果是:

2017-08-16 20:41:34.006 TestNew[3937:1710027] 主队列同步--任务1--线程是:<NSThread: 0x60000007e180>{number = 1, name = main}
2017-08-16 20:41:34.006 TestNew[3937:1710027] 主队列同步--任务2--线程是:<NSThread: 0x60000007e180>{number = 1, name = main}
2017-08-16 20:41:34.007 TestNew[3937:1710027] 主队列同步--任务3--线程是:<NSThread: 0x60000007e180>{number = 1, name = main}

至此,我们已经把同步异步、并行队列串行队列的组合说完了。

GCD线程通信

在实际开发中,我们会将耗时的操作在后台线程中进行,例如下载图片,图片下载完成后,后台线程就应该通知主线程去更新页面了。这个时候就要用到线程之间的通信。

线程中的通信主要是:

(1)一个线程传递数据给另一线程;

(2)一个线程执行完任务后,通知另一个线程开始执行

下面举例,线程之间的通信:

    UIImageView *iamgeView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 64, [UIScreen mainScreen].bounds.size.width, 200)];
    
    [self.view addSubview:iamgeView];
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
       
        //开启多线程下载图片
        NSURL *url = [[NSURL alloc]initWithString:@"https://www.baidu.com/img/bd_logo1.png"];
        
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        UIImage *image = [UIImage imageWithData:data];
        
        // 图片下载太快,睡一会
        [NSThread sleepForTimeInterval:2];
        
        // 图片下载完毕,在主线程跟新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            iamgeView.image = image;
        });
        
    });
	

这段代码先开一个线程下载图片,下载好之后,睡两秒,通知主线程进行UI跟新。

dispatch_after

上面的例子,我们使用了sleep让当前线程休眠2S,我们也可以使用dispatch_after。

需要注意的是dispatch_after并不是2S之后执行任务,而是2S之后将任务加入到队列中。如果有严格的时间要求,就不能用这个方法,大概的时间可以使用这个方法。

    UIImageView *iamgeView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 64, [UIScreen mainScreen].bounds.size.width, 200)];
    
    [self.view addSubview:iamgeView];
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
       
        //开启多线程下载图片
        NSURL *url = [[NSURL alloc]initWithString:@"https://www.baidu.com/img/bd_logo1.png"];
        
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        UIImage *image = [UIImage imageWithData:data];
        
        // 图片下载太快,睡一会
        dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 5ull *NSEC_PER_SEC);
        dispatch_after(time,dispatch_get_main_queue(), ^{
            iamgeView.image = image;

        });

	

ull是C语言的数值字面量,显示的表明类型时使用的字符串(unsigned long long)。

在上面的例子中,主线程是在主线程的runloop中执行,假如这个runloop每隔1/50秒循环一次,那么这个block最少在3秒后执行,最多再3秒+1/50秒后执行。

dispatch_barrier_async GCD栅栏

dispatch_barrier_async一定是要和并行队列一起使用的。当队列中的任务需要分成两组,而第二组需要在第一组完成之后在进行,那么就可以使用dispacth_barrier_async.

举个例子,这次我们要读取两张图片,然后再去计算图片的大小,很明显,我们应该在把下载图片这两个任务完成,再去完成计算图片大小

    
       
        dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
        
        //下载第一张图片
        
        // 异步执行
        dispatch_async(queue, ^{
             NSLog(@"栅栏:并发异步--下载第一张图片   %@",[NSThread currentThread]);
            NSURL *url = [[NSURL alloc]initWithString:@"https://www.baidu.com/img/bd_logo1.png"];
            
            NSData *data = [NSData dataWithContentsOfURL:url];
            
            _firstImage = [UIImage imageWithData:data];
        });
        
        //下载第二张图片
        
        // 异步执行
        dispatch_async(queue, ^{
            NSLog(@"栅栏:并发异步--下载第二张图片   %@",[NSThread currentThread]);
            NSURL *url = [[NSURL alloc]initWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1502964291259&di=e24fedeaab6ba7342deb01d83c24f627&imgtype=0&src=http%3A%2F%2Fimg1.cache.netease.com%2Fcatchpic%2FE%2FED%2FED1C89F9BBADDC32C1283530A47E910B.jpg"];
            
            NSData *data = [NSData dataWithContentsOfURL:url];
            
            _secondImage = [UIImage imageWithData:data];
        });

        
        // 栅栏
        
        dispatch_barrier_async(queue, ^{
            NSLog(@"-----------------栅栏------------------");
            NSLog(@"栅栏:   %@",[NSThread currentThread]);
        });
        
        // 第二部分任务
        
        dispatch_async(queue, ^{
           
            NSLog(@"模拟计算图片大小");
            NSLog(@"模拟计算图片大小的线程:   %@",[NSThread currentThread]);
            
        });

	

输出结果是:

2017-08-17 15:17:12.800 TestNew[2374:764761] 栅栏:并发异步--下载第二张图片   <NSThread: 0x60000007e580>{number = 4, name = (null)}
2017-08-17 15:17:12.800 TestNew[2374:764762] 栅栏:并发异步--下载第一张图片   <NSThread: 0x60000007e4c0>{number = 3, name = (null)}
2017-08-17 15:17:13.222 TestNew[2374:764761] -----------------栅栏------------------
2017-08-17 15:17:13.222 TestNew[2374:764761] 栅栏:   <NSThread: 0x60000007e580>{number = 4, name = (null)}
2017-08-17 15:17:13.223 TestNew[2374:764761] 模拟计算图片大小
2017-08-17 15:17:13.223 TestNew[2374:764761] 模拟计算图片大小的线程:   <NSThread: 0x60000007e580>{number = 4, name = (null)}
	

上面的例子中,我们只用的自己创建的并行队列,那么如果使用globalQueue呢?有人觉得global-queue也是一个并行队列。

//下载第一张图片
        
        // 异步执行
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
             NSLog(@"栅栏:并发异步--下载第一张图片   %@",[NSThread currentThread]);
            NSURL *url = [[NSURL alloc]initWithString:@"https://www.baidu.com/img/bd_logo1.png"];
            
            NSData *data = [NSData dataWithContentsOfURL:url];
            
            _firstImage = [UIImage imageWithData:data];
        });
        
        //下载第二张图片
        
        // 异步执行
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"栅栏:并发异步--下载第二张图片   %@",[NSThread currentThread]);
            NSURL *url = [[NSURL alloc]initWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1502964291259&di=e24fedeaab6ba7342deb01d83c24f627&imgtype=0&src=http%3A%2F%2Fimg1.cache.netease.com%2Fcatchpic%2FE%2FED%2FED1C89F9BBADDC32C1283530A47E910B.jpg"];
            
            NSData *data = [NSData dataWithContentsOfURL:url];
            
            _secondImage = [UIImage imageWithData:data];
        });

        
        // 栅栏
        
        dispatch_barrier_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"-----------------栅栏------------------");
            NSLog(@"栅栏:   %@",[NSThread currentThread]);
        });
        
        // 第二部分任务
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
           
            NSLog(@"模拟计算图片大小");
            NSLog(@"模拟计算图片大小的线程:   %@",[NSThread currentThread]);
            
        });
		

输出结果是:

2017-08-17 15:21:18.602 TestNew[2436:789411] 模拟计算图片大小
2017-08-17 15:21:18.602 TestNew[2436:789424] -----------------栅栏------------------
2017-08-17 15:21:18.602 TestNew[2436:789408] 栅栏:并发异步--下载第二张图片   <NSThread: 0x60000026b640>{number = 4, name = (null)}
2017-08-17 15:21:18.602 TestNew[2436:789409] 栅栏:并发异步--下载第一张图片   <NSThread: 0x608000269240>{number = 3, name = (null)}
2017-08-17 15:21:18.602 TestNew[2436:789411] 模拟计算图片大小的线程:   <NSThread: 0x60000026ba80>{number = 5, name = (null)}
2017-08-17 15:21:18.602 TestNew[2436:789424] 栅栏:   <NSThread: 0x608000269a00>{number = 6, name = (null)}
	

很不幸,并没有按照我们的预想,所以我们可以说使用栅栏一定要用自己创建的并行队列。

综上,栅栏在访问数据库时,可以让读写分开,让读操作并行,在栅栏中进行写操作,这样会大大的提高数据库的访问速度。

dispatch_apply GCD快速迭代

dispatch_apply将一个相同的任务按指定的数量加入到队列中,并等待这些任务全部执行完毕。        

 dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
        NSLog(@"%zu",index);
    });

输出结果是:

2017-08-17 15:34:04.028 TestNew[2520:827569] 0
2017-08-17 15:34:04.028 TestNew[2520:827737] 1
2017-08-17 15:34:04.028 TestNew[2520:827736] 3
2017-08-17 15:34:04.028 TestNew[2520:827743] 2
2017-08-17 15:34:04.029 TestNew[2520:827569] 4
2017-08-17 15:34:04.029 TestNew[2520:827737] 5
2017-08-17 15:34:04.029 TestNew[2520:827736] 6
2017-08-17 15:34:04.029 TestNew[2520:827569] 8
2017-08-17 15:34:04.029 TestNew[2520:827743] 7
2017-08-17 15:34:04.029 TestNew[2520:827737] 9

dispatch_once 执行一次

保证任务只被执行一次。可以用于设计单例。

static dispatch_once_t once;
dispatch_once(&once, ^{
    NSLog(@"只执行一次");
});

变更已生成队列的优先级 dispatch_set_target_queue

我们用dispatch_queue_creat生成的队列都是默认优先级,与global_dispatch_queue的优先级一样,我们可以使用dispatch_set_target_queue变更队列的优先级。

    // 需要改变优先级的队列
    dispatch_queue_t myQueue = dispatch_queue_create("queuq", DISPATCH_QUEUE_CONCURRENT);
    
    // 作为参数的global_queue,用这个global_queue的优先级去赋值给myQueue
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
    
    dispatch_set_target_queue(myQueue, globalQueue);

dispatch_suspend/dispatch_resume

当追加了大量的任务到queue中时,在追加的过程中,希望不执行刚追加到队列中的任务,就可以先将队列挂起

     dispatch_suspend(myQueue);

之后就可以恢复:

     dispatch_resume(myQueue);

以下还有两个GCD中比较重要的概念

dispatch_group 队列组

我们异步执行了几个耗时的操作,然后在这些操作都完成之后,我们想执行最后的处理。例如我们有数据需要计算,在这些数据都计算请求完成之后,进行求和。我们就可以使用队列组。

  dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"-------任务1--------");
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"-------任务2--------");
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"-------任务3--------");
    });
    
    // 三个任务都执行完毕之后,进行下一步
    dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"---------执行完任务1、2、3之后,执行此任务");
    });

执行结果如下:

2017-08-17 16:53:39.392 TestNew[2997:1105112] -------任务3--------
2017-08-17 16:53:39.392 TestNew[2997:1105109] -------任务2--------
2017-08-17 16:53:41.397 TestNew[2997:1105110] -------任务1--------
2017-08-17 16:53:41.398 TestNew[2997:1105110] ---------执行完任务1、2、3之后,执行此任务

尽管第一个任务休眠了2秒,但是最后一个任务依然会等待第一个任务完成再进行。

dispatch_semaphore GCD信号量

如果我们需要等到两个任务完成再去完成一个任务,其实我们用前面的dispatch_group就可以轻松的完成,但是加入我们的前两个任务是通过网络去获取数据,然后再执行第三个任务,这个时候就会出问题,因为我们将网络请求的代码放在一个block中作为一个任务,这个任务在网络连接时,又会开启新的线程。而任务1和任务2会继续往下执行,不会等待网络请求完成。所以我们需要细粒度更高的信号量去完成这个功能。

我们先看看与dispatch_semaphore相关的三个方法:

    // 创建一个信号量,初始值是1
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    //等待semaphore的值>=1,否则一直等待,或者semaphore的值大于1时,进行-1
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    //将semaphore的值+1
    dispatch_semaphore_signal(semaphore);
    

如果semaphore计数大于等于1,计数-1,返回,程序继续运行。如果计数为0,则等待。

假如我们不用dispatch_semaphore,就会出现在网络请求还未完成的时候就去进行最后的处理:

dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        
        // 1 获得管理者
        AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
        NSString *urlString = @"https://xxxxx.us/api/v1/banners";
        [manager GET:urlString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
            
            NSLog(@"任务1---取得网络数据成功----%@",[NSThread currentThread]);
            
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            NSLog(@"任务1---取得网络数据失败----%@",[NSThread currentThread]);
        }];
        
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        
        AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
        NSString *strUrl = @"https://xxxxx.us/api/v1/articles/with_position?page=1&position=home_bottom";
        
        [manager GET:strUrl parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
            NSLog(@"任务2---取得网络数据成功----%@",[NSThread currentThread]);
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            NSLog(@"任务2---取得网络数据成功----%@",[NSThread currentThread]);
        }];
    });
    
    
    
    // 三个任务都执行完毕之后,进行下一步
    dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"执行完任务1和2,执行最后一个任务----%@",[NSThread currentThread]);
    });

输出结果为:

2017-08-17 19:20:28.099 TestNew[3612:1358331] 执行完任务1和2,执行最后一个任务----<NSThread: 0x60800006c780>{number = 3, name = (null)}
2017-08-17 19:20:31.119 TestNew[3612:1357843] 任务1---取得网络数据成功----<NSThread: 0x608000063540>{number = 1, name = main}
2017-08-17 19:20:31.805 TestNew[3612:1357843] 任务2---取得网络数据成功----<NSThread: 0x608000063540>{number = 1, name = main}

可以看到耗时操作在未完成的情况下,就执行了最后一个操作,这就是前面所说的,网络请求又新开了一个线程。

下面利用dispatch_semaphore来解决这个问题:

dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        
        // 1 获得管理者
        AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
        NSString *urlString = @"https://XXXX.us/api/v1/banners";
        
        // 创建信号量
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        
        [manager GET:urlString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
            
            // 信号量+1,此时信号量>=1,继续执行
            dispatch_semaphore_signal(semaphore);
            
            NSLog(@"任务1---取得网络数据成功----%@",[NSThread currentThread]);
            
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            
            // 信号量+1,此时信号量>=1,继续执行
            dispatch_semaphore_signal(semaphore);
            
            NSLog(@"任务1---取得网络数据失败----%@",[NSThread currentThread]);
        }];
        
        // 如果没有请求成功,就一直等待,此时semaphore信号量是0
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        
        AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
        NSString *strUrl = @"https://XXXX.us/api/v1/articles/with_position?page=1&position=home_bottom";
        
        // 创建信号量
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        
        [manager GET:strUrl parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
            
            // 信号量+1,此时信号量>=1,继续执行
            dispatch_semaphore_signal(semaphore);

            
            NSLog(@"任务2---取得网络数据成功----%@",[NSThread currentThread]);
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            
            // 信号量+1,此时信号量>=1,继续执行
            dispatch_semaphore_signal(semaphore);

            NSLog(@"任务2---取得网络数据成功----%@",[NSThread currentThread]);
        }];
        
        // 如果没有请求成功,就一直等待,此时semaphore信号量是0
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    });
    
    

    // 三个任务都执行完毕之后,进行下一步
    dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"执行完任务1和2,执行最后一个任务----%@",[NSThread currentThread]);
    });

输出结果为:

2017-08-17 19:48:26.303 TestNew[3787:1494537] 任务2---取得网络数据成功----<NSThread: 0x60800007e900>{number = 1, name = main}
2017-08-17 19:48:26.952 TestNew[3787:1494537] 任务1---取得网络数据成功----<NSThread: 0x60800007e900>{number = 1, name = main}
2017-08-17 19:48:26.953 TestNew[3787:1494989] 执行完任务1和2,执行最后一个任务----<NSThread: 0x60800026a540>{number = 5, name = (null)}

此时就可以争取的输出了。

至此,GCD的主要功能就完了。

NSOperation

NSOperation是对GCD的封装,需要和NSOperationQueue配合实现多线程。

NSOperation使用步骤

(1)创建一个任务(操作)NSOperation

(2)创建一个队列(主队列、其他队列)NSOperationQueue

(3)将操作放到队列中

NSOperation

NSOperation一共有三种:

(1)NSInvocationOperation,创建后,需要start

(2)NSBlockOperation

(3)自定义的NSOperation

NSOperationQueue

一共有两种NSOperationQueue:

(1)主队列 :主队列只能串行,并且在主线程上运行。

(2)非主队列:既包括串行队列,又包括并行队列

需要注意的是:

(1)非主队列有一个属性maxConcurrentOperationCount(最大并发数),默认为-1.默认开启多线程

(2)人为设置maxConcurrentOperationCount=1,不开启多线程,任务顺序串行执行

(3)人为设置人为设置maxConcurrentOperationCount>1,开启多线程

(4)并不是人为设置maxConcurrentOperationCount越大越好,系统会调整。

NSInvocationOperation/NSBlockOperation + 非主队列

开启新的线程,异步并发。

    // 创建NSInvocationOperation
    NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
    
    // 创建NSInvocationOperation
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"blockOperation + 非主队列---%@",[NSThread currentThread]);

    }];
    
    
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperation:operation];
    [queue addOperation:blockOperation];

输出结果为:

2017-08-17 20:54:18.338 TestNew[4027:1658946] NSInvocationOperation + 非主队列---<NSThread: 0x608000079400>{number = 4, name = (null)}
2017-08-17 20:54:18.338 TestNew[4027:1658948] blockOperation + 非主队列---<NSThread: 0x6080000793c0>{number = 3, name = (null)}

NSBlockOperation + addExecutionBlock方法

NSBlockOperation有一个addExecutionBlock方法,会让开启多线程,但是初始化NSBlockOperation的任务必须在主线程完成

NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"blockOperation + 主队列---%@",[NSThread currentThread]);

    }];
    

    [blockOperation addExecutionBlock:^{
       NSLog(@"blockOperation + 非主队列---%@",[NSThread currentThread]);
    }];
    
    [blockOperation addExecutionBlock:^{
        NSLog(@"blockOperation + 非主队列---%@",[NSThread currentThread]);
    }];
    
    [blockOperation start];

输出结果为:

2017-08-17 21:00:27.138 TestNew[4089:1692473] blockOperation + 主队列---<NSThread: 0x600000069f00>{number = 1, name = main}
2017-08-17 21:00:27.138 TestNew[4089:1692947] blockOperation + 非主队列---<NSThread: 0x600000074f80>{number = 4, name = (null)}
2017-08-17 21:00:27.138 TestNew[4089:1692965] blockOperation + 非主队列---<NSThread: 0x60000006d140>{number = 3, name = (null)}

继承NSOperation

NSOperation的第三种实现方式,继承NSOperation,复写他的main函数;

DHOperation.m 代码如下:

#import "DHOperation.h"

@implementation DHOperation

-(void)main{
    
    NSLog(@"自定义NSOperation-----%@",[NSThread currentThread]);
    
}

使用NSOperation代码如下:

// 创建NSInvocationOperation
    DHOperation *operation1 = [[DHOperation alloc]init];
    
    // 创建NSInvocationOperation
    DHOperation *operation2 = [[DHOperation alloc]init];
    
    // 创建NSInvocationOperation
    DHOperation *operation3 = [[DHOperation alloc]init];
    

   
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];

运行结果如下:

2017-08-19 15:21:27.976 TestNew[1277:200770] 自定义NSOperation-----<NSThread: 0x608000267cc0>{number = 3, name = (null)}
2017-08-19 15:21:27.980 TestNew[1277:200801] 自定义NSOperation-----<NSThread: 0x600000269000>{number = 5, name = (null)}
2017-08-19 15:21:27.980 TestNew[1277:200773] 自定义NSOperation-----<NSThread: 0x600000264940>{number = 4, name = (null)}

最大并发数实现串行

前面我们说了,队列有一个最大并发数,如果我们把这个并发数设置为1,就可以让队列中的任务一个一个顺序执行,也就是串行。

// 创建NSInvocationOperation
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
    
    // 创建NSInvocationOperation
    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
    
    // 创建NSInvocationOperation
    NSInvocationOperation *operation3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
    

   
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    queue.maxConcurrentOperationCount  = 1;
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];

test函数为:

-(void)test{
    NSLog(@"NSInvocationOperation + 非主队列---%@",[NSThread currentThread]);
}

输出结果为:

2017-08-19 14:55:06.689 TestNew[1128:130681] NSInvocationOperation + 非主队列---<NSThread: 0x600000069680>{number = 3, name = (null)}
2017-08-19 14:55:06.692 TestNew[1128:130681] NSInvocationOperation + 非主队列---<NSThread: 0x600000069680>{number = 3, name = (null)}
2017-08-19 14:55:06.693 TestNew[1128:130681] NSInvocationOperation + 非主队列---<NSThread: 0x600000069680>{number = 3, name = (null)}

NSOperation操作的依赖

试想一个情况,一个操作,需要在前一个操作的数据,就必须等前一个操作取得数据后再进行本操作,那么操作2就必须依赖于操作1.NSOperation提供了依赖这个方法来实现这个功能。

GCD和NSOperation的关系

(1)GCD的底层是语言API构成的,而NSOperation是OC对象构成的。GCD中任务是在block 中构成,NSOperation直接是一个对象构成;

(2) GCD中无法停止加入到队列中的任务,NSOperation中的任务可以停止;

(3)KVO可以应用在NSOperation中,可以监听一个operation是否执行完毕或者取消;

(4)NSOperation可以设置优先级,可以让同一个队列中的任务先后执行,而GCD只能区别队列的优先级;

(5)可以继承NSOperation。添加更多的属性,增加灵活度。