什么是多线程?
计算机在运行一段程序的时候,会把该程序的CPU命令列配置到内存中,然后按照顺序一个一个执行命令列,这样1个CPU执行的CPU命令列为一条无分叉路径就是线程。
而有多条这样的执行指令列的路径存在时即为多线程。
iOS实现多线程有4种方法
- pthreads
- NSThread
- GCD
- NSOperation & NSOperationQueuef
这里我们主要讲GCD
一、Dispatch Queue和线程的关系
什么是Dispatch Queue?
如其名称,是执行处理的等待队列。当我们通过dispatch_async等函数把Block加入Dispatch Queue后,Dispatch Queue按照追加的顺序(FIFO)执行处理。
Dispatch Queue的种类
- Serial Dispatch Queue(串行队列) ——等待现在执行中处理结束再加入队列
- Concurrent Dispatch Queue(并发队列) ——不等待现在执行中处理结束,直接加入队列
用代码说明:
Serial Dispatch Queue
dispatch_queue_t serial_queue = dispatch_queue_create("come.tanpei", DISPATCH_QUEUE_SERIAL);
dispatch_async(serial_queue, ^{
NSLog(@"block 1");
});
dispatch_async(serial_queue, ^{
NSLog(@"block 2");
});
dispatch_async(serial_queue, ^{
NSLog(@"block 3");
});
dispatch_async(serial_queue, ^{
NSLog(@"block 4");
});
输出如下:
2017-09-27 11:43:40.230126+0800 aegewgr[4327:1296458] block 1
2017-09-27 11:43:40.230335+0800 aegewgr[4327:1296458] block 2
2017-09-27 11:43:40.230461+0800 aegewgr[4327:1296458] block 3
2017-09-27 11:43:40.230548+0800 aegewgr[4327:1296458] block 4
这里Serial Dispatch Queue只会使用一个线程,因为它是串行队列,只会当一个处理执行完了才会将下一个任务交给线程处理。
Concurrent Dispatch Queue
dispatch_queue_t concurrent_queue = dispatch_queue_create("come.tanpei", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrent_queue, ^{
NSLog(@"block 1");
});
dispatch_async(concurrent_queue, ^{
NSLog(@"block 2");
});
dispatch_async(concurrent_queue, ^{
NSLog(@"block 3");
});
dispatch_async(concurrent_queue, ^{
NSLog(@"block 4");
});
输出如下:
2017-09-27 11:45:09.057505+0800 aegewgr[4349:1304484] block 3
2017-09-27 11:45:09.057505+0800 aegewgr[4349:1304483] block 1
2017-09-27 11:45:09.057522+0800 aegewgr[4349:1304486] block 4
2017-09-27 11:45:09.057505+0800 aegewgr[4349:1304485] block 2
block的执行完成
是随机的,因为他们虽然是按顺序把任务提交给线程,但是因为不需要等待前一个任务执行,所以几乎是同时交给线程处理的。所以这里会使用多个线程,而具体线程数的多少由XNU内核决定。
二、Dispatch Queue的使用
1、获取队列
在使用Dispatch Queue的时候我们可以通过dispatch_queue_create
函数创建队列,也可以获取系统给我们提供的队列。系统给我们提供了两种队列
2、同步与异步
- dispatch_async表示异步:将指定的Block”非同步“加入Dispatch Queue,不做任何等待
- dispatch_sync表示同步:将指定的Block”同步“的加入Dispatch Queue,在Block结束之前,dispatch_sync函数会一直等待
3、死锁
由于dispatch_sync会等待Block执行结束才会继续往下执行,所以会产生死锁的情况
我们直接在主线程中同步加入一个Blcok:
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_sync(main_queue, ^{
NSLog(@"main queue");
});
NSLog(@"go on");
无任何输出,程序直接卡死了。这就是造成了死锁。
因为该源代码在main_queue(主线程)中加入一个加入一个指定的Block,并等待其执行结束。而由于main_queue是一个串行队列,它要等当前线程中的任务处理完后才会把队列中的任务提交到主线程,而主线程又在等待这段代码执行,所以造成了相互等待,就产生了死锁。(而并发队列不会产生死锁)
如:
dispatch_queue_t global_queue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT);
dispatch_sync(global_queue, ^{
NSLog(@"global_queue out");
dispatch_sync(global_queue, ^{
NSLog(@"global_queue in");
});
});
输出如下:
2017-09-27 16:11:56.332317+0800 aegewgr[4723:1590202] global_queue out
2017-09-27 16:11:56.332446+0800 aegewgr[4723:1590202] global_queue in
所以产生死锁的话一般都是在串行队列中并且是在一个线程中同步往这个线程提交一个Block。
4、Dispatch Group(派发分组)
Dispatch Group是GCD的一项特性,能够把任务分组。调用者在这组任务执行完毕后会得到通知,并做相应的处理。
创建:dispatch_group_t group = dispatch_group_create();
同样的,它也有
dispatch_group_async(dispatch_group_t _Nonnull group, dispatch_queue_t _Nonnull queue, <#^(void)block#>)
和dispatch_sync
函数没有什么区别,它只是多了一个dispatch_group_t参数,来把任务进行分组。
还有一种方法能把任务加入dispatch_group,那就是下面这对情侣:
dispatch_group_enter(dispatch_group_t group);
dispatch_group_leave(dispatch_group_t group);
记住,这对情侣一定要成对出现,dispatch_group_enter就是标志下面的代码要加入dispatch_group。dispatch_group_leave就是表示加入dispatch_group的代码结束。也就是说dispatch_group_enter和dispatch_group_leave之间的代码就是加入dispatch_group中的。
说了这么多,把一个队列加入dispatch_group后有什么用呢?主要就是一组相似的操作结束后,你可以通过dispatch_block_notify(dispatch_block_t block, dispatch_queue_t queue, dispatch_block_t notification_block)
函数来获得通知,并进行相应的处理。Block参数就是你要添加的处理。
当然,如果你想设置一个等待时间,可以使用dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)
函数,该函数设置了一个等待时间也就是说程序要一直阻塞当前线程直到group中的任务执行完毕或者超过等待时间,才会继续往下执行。
而dispatch_block_notify
函数不会阻塞当前线程,它只是指定了一个group任务执行完后的回调。
需要举个栗子吗?
好吧,还是举个栗子吧。
dispatch_queue_t global_queue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT);
dispatch_group_t group = dispatch_group_create();
;
dispatch_group_async(group, global_queue, ^{
NSLog(@"task 1");
});
dispatch_group_async(group, global_queue, ^{
NSLog(@"task 2");
});
dispatch_group_async(group, global_queue, ^{
NSLog(@"task 3");
});
dispatch_group_notify(group, global_queue, ^{
NSLog(@"notify");
});
NSLog(@"other task");
输出如下:
2017-09-27 17:06:37.795564+0800 aegewgr[4983:1713915] other task
2017-09-27 17:06:37.795571+0800 aegewgr[4983:1714182] task 3
2017-09-27 17:06:37.795578+0800 aegewgr[4983:1714181] task 1
2017-09-27 17:06:37.795578+0800 aegewgr[4983:1714183] task 2
2017-09-27 17:06:37.795813+0800 aegewgr[4983:1714183] notify
可以看到dispatch_group_notify并没有阻塞当前线程,而且它提交的Block一定是当group中的所有任务执行完后才会执行。另外,这里的queue可以不是一个queue,你可以使用任意其它queue,不过最好是并发队列,如果是串行队列,任务会按顺序一个一个执行,那使用group的意义就不大了。
看看dispatch_group_wait
dispatch_queue_t global_queue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT);
dispatch_group_t group = dispatch_group_create();
;
dispatch_group_async(group, global_queue, ^{
NSLog(@"task 1");
});
dispatch_group_async(group, global_queue, ^{
NSLog(@"task 2");
});
dispatch_group_async(group, global_queue, ^{
NSLog(@"task 3");
});
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1));
NSLog(@"other task");
输出如下:
2017-09-27 17:10:10.968133+0800 aegewgr[5002:1726567] task 2
2017-09-27 17:10:10.968133+0800 aegewgr[5002:1726568] task 1
2017-09-27 17:10:10.968140+0800 aegewgr[5002:1726569] task 3
2017-09-27 17:10:10.968133+0800 aegewgr[5002:1726443] other task
可以看到dispatch_group_wait函数阻塞了当前线程,只有当group中的所有任务执行完后线程才会继续往下执行。
5 、其它相关函数
- dispatch_barrier_async和dispatch_barrier_sync(栅栏)
这两个函数的作用差不多,都是把它前面和它后面的函数分隔开。使它前面的任务先执行,再执行它添加的任务,最后执行它后面的任务。
那么它们有什么区别呢?
当然从名字就能看出来,就是提交任务的方式不同,一个是同步一个是异步,同步和异步的区别前面有解释,如果忘了的话,可以再回去看看。
- Dispatch Semaphore(信号量)
信号量其实就是用来保证访问资源的线程数,当信号量大于等于1时,资源可以访问,否则无法访问资源,直到其它线程释放资源。
这里主要有三个函数:
dispatch_semaphore_t dispatch_semaphore_create(long value); //创建一个dispatch_semaphore_t,value为初始信号量
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); //信号量-1
long dispatch_semaphore_signal(dispatch_semaphore_t dsema); //信号量+1
怎么用呢?
还是举个栗子吧:
假如有两个资源,但是同时有三个线程想要访问,就可以使用信号量进行控制:
//crate的value表示,最多几个资源可访问
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//任务1
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 1");
sleep(1);
NSLog(@"complete task 1");
dispatch_semaphore_signal(semaphore);
});
//任务2
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 2");
sleep(1);
NSLog(@"complete task 2");
dispatch_semaphore_signal(semaphore);
});
//任务3
dispatch_async(quene, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"run task 3");
sleep(1);
NSLog(@"complete task 3");
dispatch_semaphore_signal(semaphore);
});
输出如下:
2017-09-27 18:04:27.590428+0800 aegewgr[5149:1860224] run task 1
2017-09-27 18:04:27.590428+0800 aegewgr[5149:1860221] run task 2
2017-09-27 18:04:28.591086+0800 aegewgr[5149:1860224] complete task 1
2017-09-27 18:04:28.591086+0800 aegewgr[5149:1860221] complete task 2
2017-09-27 18:04:28.591386+0800 aegewgr[5149:1860219] run task 3
2017-09-27 18:04:29.591845+0800 aegewgr[5149:1860219] complete task 3
假如把信号量设置为3呢?
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
输出如下:
2017-09-27 18:08:37.535634+0800 aegewgr[5169:1873722] run task 2
2017-09-27 18:08:37.535637+0800 aegewgr[5169:1873721] run task 1
2017-09-27 18:08:37.535636+0800 aegewgr[5169:1873723] run task 3
2017-09-27 18:08:38.539585+0800 aegewgr[5169:1873723] complete task 3
2017-09-27 18:08:38.539585+0800 aegewgr[5169:1873721] complete task 1
2017-09-27 18:08:38.539585+0800 aegewgr[5169:1873722] complete task 2
- dispatch_once
此函数在我们创建单例的时候经常会用到,就是可以保证在应用程序执行中该函数只执行一次。即使在多线程环境也,也可以保证百分百的安全。
写在文末
本来是打算写一篇关于多线程的文章的,因为想把GCD介绍的全面一点,所以篇幅就有点长了,另外三种方式请看这里。