什么是多线程?
计算机在运行一段程序的时候,会把该程序的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执行处理
Dispatch Queue的种类
Serial Dispatch Queue(串行队列) ——等待现在执行中处理结束再加入队列
Concurrent Dispatch Queue(并发队列) ——不等待现在执行中处理结束,直接加入队列
Serial Dispatch Queue
Concurrent Dispatch Queue
用代码说明:
Serial Dispatch Queue
dispatch_queue_tserial_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-2711:43:40.230126+0800aegewgr[4327:1296458]block1
2017-09-2711:43:40.230335+0800aegewgr[4327:1296458]block2
2017-09-2711:43:40.230461+0800aegewgr[4327:1296458]block3
2017-09-2711:43:40.230548+0800aegewgr[4327:1296458]block4
这里Serial Dispatch Queue只会使用一个线程,因为它是串行队列,只会当一个处理执行完了才会将下一个任务交给线程处理。
Concurrent Dispatch Queue
dispatch_queue_tconcurrent_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-2711:45:09.057505+0800aegewgr[4349:1304484]block3
2017-09-2711:45:09.057505+0800aegewgr[4349:1304483]block1
2017-09-2711:45:09.057522+0800aegewgr[4349:1304486]block4
2017-09-2711:45:09.057505+0800aegewgr[4349:1304485]block2
block的执行完成
是随机的,因为他们虽然是按顺序把任务提交给线程,但是因为不需要等待前一个任务执行,所以几乎是同时交给线程处理的。所以这里会使用多个线程,而具体线程数的多少由XNU内核决定。
Concurrent Dispatch Queue的执行
二、Dispatch Queue的使用
1、获取队列
在使用Dispatch Queue的时候我们可以通过dispatch_queue_create函数创建队列,也可以获取系统给我们提供的队列。系统给我们提供了两种队列
系统提供的Dispatch Queue
2、同步与异步
dispatch_async表示异步:将指定的Block”非同步“加入Dispatch Queue,不做任何等待
异步执行
dispatch_sync表示同步:将指定的Block”同步“的加入Dispatch Queue,在Block结束之前,dispatch_sync函数会一直等待
同步执行
3、死锁
由于dispatch_sync会等待Block执行结束才会继续往下执行,所以会产生死锁的情况
我们直接在主线程中同步加入一个Blcok:
dispatch_queue_tmain_queue = dispatch_get_main_queue();
dispatch_sync(main_queue, ^{
NSLog(@"main queue");
});
NSLog(@"go on");
无任何输出,程序直接卡死了。这就是造成了死锁。
因为该源代码在main_queue(主线程)中加入一个加入一个指定的Block,并等待其执行结束。而由于main_queue是一个串行队列,它要等当前线程中的任务处理完后才会把队列中的任务提交到主线程,而主线程又在等待这段代码执行,所以造成了相互等待,就产生了死锁。(而并发队列不会产生死锁)
如:
dispatch_queue_tglobal_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-2716:11:56.332317+0800aegewgr[4723:1590202]global_queueout
2017-09-2716:11:56.332446+0800aegewgr[4723:1590202]global_queuein
所以产生死锁的话一般都是在串行队列中并且是在一个线程中同步往这个线程提交一个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_tgroup);
dispatch_group_leave(dispatch_group_tgroup);
记住,这对情侣一定要成对出现,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_tglobal_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-2717:06:37.795564+0800aegewgr[4983:1713915]othertask
2017-09-2717:06:37.795571+0800aegewgr[4983:1714182]task3
2017-09-2717:06:37.795578+0800aegewgr[4983:1714181]task1
2017-09-2717:06:37.795578+0800aegewgr[4983:1714183]task2
2017-09-2717:06:37.795813+0800aegewgr[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_tgroup= 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-2717:10:10.968133+0800aegewgr[5002:1726567]task2
2017-09-2717:10:10.968133+0800aegewgr[5002:1726568]task1
2017-09-2717:10:10.968140+0800aegewgr[5002:1726569]task3
2017-09-2717:10:10.968133+0800aegewgr[5002:1726443]othertask
可以看到dispatch_group_wait函数阻塞了当前线程,只有当group中的所有任务执行完后线程才会继续往下执行。
5 、其它相关函数
dispatch_barrier_async和dispatch_barrier_sync(栅栏)
这两个函数的作用差不多,都是把它前面和它后面的函数分隔开。使它前面的任务先执行,再执行它添加的任务,最后执行它后面的任务。
那么它们有什么区别呢?
当然从名字就能看出来,就是提交任务的方式不同,一个是同步一个是异步,同步和异步的区别前面有解释,如果忘了的话,可以再回去看看。
Dispatch Semaphore(信号量)
信号量其实就是用来保证访问资源的线程数,当信号量大于等于1时,资源可以访问,否则无法访问资源,直到其它线程释放资源。
这里主要有三个函数:
dispatch_semaphore_tdispatch_semaphore_create(longvalue);//创建一个dispatch_semaphore_t,value为初始信号量
longdispatch_semaphore_wait(dispatch_semaphore_tdsema,dispatch_time_ttimeout);//信号量-1
longdispatch_semaphore_signal(dispatch_semaphore_tdsema);//信号量+1
怎么用呢?
还是举个栗子吧:
假如有两个资源,但是同时有三个线程想要访问,就可以使用信号量进行控制:
//crate的value表示,最多几个资源可访问
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_queue_tquene = 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-2718:04:27.590428+0800aegewgr[5149:1860224]runtask1
2017-09-2718:04:27.590428+0800aegewgr[5149:1860221]runtask2
2017-09-2718:04:28.591086+0800aegewgr[5149:1860224]completetask1
2017-09-2718:04:28.591086+0800aegewgr[5149:1860221]completetask2
2017-09-2718:04:28.591386+0800aegewgr[5149:1860219]runtask3
2017-09-2718:04:29.591845+0800aegewgr[5149:1860219]completetask3
假如把信号量设置为3呢?
dispatch_semaphore_tsemaphore = dispatch_semaphore_create(3);
输出如下:
2017-09-2718:08:37.535634+0800aegewgr[5169:1873722]runtask2
2017-09-2718:08:37.535637+0800aegewgr[5169:1873721]runtask1
2017-09-2718:08:37.535636+0800aegewgr[5169:1873723]runtask3
2017-09-2718:08:38.539585+0800aegewgr[5169:1873723]completetask3
2017-09-2718:08:38.539585+0800aegewgr[5169:1873721]completetask1
2017-09-2718:08:38.539585+0800aegewgr[5169:1873722]completetask2
dispatch_once
此函数在我们创建单例的时候经常会用到,就是可以保证在应用程序执行中该函数只执行一次。即使在多线程环境也,也可以保证百分百的安全。