码迷,mamicode.com
首页 > 编程语言 > 详细

多线程一

时间:2016-04-22 19:16:49      阅读:250      评论:0      收藏:0      [点我收藏+]

标签:







多线程

多线程

基本概念


  1. 进程:一个具有一定独立功能的程序关于某个数据结合的一次运行活动。可以理解成一个运行中的应用程序。
  2. 线程:程序执行流的最小单元,线程是进程中的一个实体。
  3. 同步:只能在当前线程按先后顺序依次执行,不开启新的线程。
  4. 异步:可以在当前线程开启多个新线程执行,可不按照顺序执行。(将任务加到队列之后立即返回。)
  5. 并发:线程执行可以同时一起进行执行。
  6. 串行:线程执行只能依次逐一先后有序的执行。
  7. 队列:装在线程任务的队形结构。

iOS多线程对比


1.NSThread

每个NSThread对象对应一个线程,真正最原始的线程。

优点:轻量级最低,相对简单。

缺点:手动管理所有的线程活动,如生命周期、线程同步、睡眠等。

2.NSOperation

自带线程管理的抽象类。

优点:自带线程周期管理,操作上可更注重自己逻辑。

缺点:面向对象的抽象类,只能实现它或者使用它定义好的两个子类

NSInvocationOperation 和 NSBlockOperation。

3.GCD

苹果开发的一个多核心编程的解决方法。开发者不需要再跟线程打交道了,只需要向队列中添加代码块即可。GCD在后端管理着一个线程池。GCD不仅仅决着你的代码块将在哪个线程被执行,并且他还可以根据可用的系统资源对这些线程进行管理。

优点:最高效,避免并发陷阱。

缺点:基于C实现。

4.选择

  • 简单安全的选择NSOperation。
  • 处理大量并发数据,又追求性能效率的选择GCD。

GCD的使用


  • NSThread封装性最差,最偏向于底层,主要基于thread使用。
  • GCD是基于C的API,直接使用比较方便,主要基于task使用。
  • NSOperation是基于GCD封装的NSObject对象,对于复杂的多线程项目使用比较方便,主要基于队列使用。

这里,主要记录一下GCD的使用。

使用GCD需要考虑的有:任务的执行方法,队列的运行方式。

任务:表示一段执行的代码,对应到代码里就是一个block。

队列:1.队列当中放的是各种待执行的任务。

  • 串行队列:一次只执行一个任务。
  • 并行队列:能够同时执行多个任务,系统会维护一个线程池来保证并行队列的执行。线程池会根据当前任务量自行安排线程的数量,以确保任务尽快执行。

队列对应到代码里是一个dispatch queue t对象。可能在非ARC下我们需要手动管理内存,但是个人觉得那些逝去的技术,就让他沉睡在过去吧,况且我也并不想手动的去管理这些与开发不相关的东西,让ARC替我去搞定吧,哈哈。当然内存管理是很重要的,之后我也会再在内存管理方面进行深入的学习,并开一篇关于内存管理的文章。言归正传,ARC下通常队列声明为strong就ok。

API以及死锁

异步(加入队列的任务会在另外的线程中执行,一旦任务被提交到队列,就立刻返回)

  • GCD当中的两个异步的API
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);

第一个API的任务以block的方式加入队列当中,第二个API以函数的形式把任务加入队列当中。二者在执行功能上并没有什么区别,都是将任务提交到队列当中就立即返回。

  • 代码示例
NSLog(@"this is main queue, i want to throw a task to global queue");
dispatch_queue_t globalQueue = dispatch_queue_create("com.liancheng.global_queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(globalQueue, ^{
    // task
});
NSLog(@"this is main queue, throw task completed");

同步(任务被加入队列之后会阻塞主线程,不会立即返回,需等任务完成之后再返回)

  • GCD当中的两个同步API
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);

  • 代码示例
NSLog(@"this is main queue, i want to throw a task to global queue");
dispatch_queue_t globalQueue = dispatch_queue_create("com.liancheng.global_queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(globalQueue, ^{
    // task
});
NSLog(@"this is main queue, throw task completed");
  • GCD中的死锁情况
dispatch_queue_t queue = dispatch_queue_create("com.liancheng.serial_queue", DISPATCH_QUEUE_SERIAL);

dispatch_async(queue, ^{
    // 到达串行队列
         dispatch_sync(queue, ^{     //发生死锁
         });
});

在串行队列当中的一个任务,以同步方式提交了一个任务到自己的队列当中,造成锁。

众所周知,串行队列中的任务一个一个执行,当执行任务的过程当中,A任务向自己这个队列当中添加了一个B任务,并且还是以同步方式提交,同步方式提交的B任务就要立刻执行(因为是同步嘛),可是A任务还没有执行完毕,由此造成死锁。

如果队列是并行队列,就不会发生这种情况了。或者把B任务加到其他的队列当中。

获取队列

主队列

 dispatch_queue_t dispatch_get_main_queue(void)

全局队列

dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);

这种方式获取的队列都市并行队列,并且队列不能修改。

  • identifier用来标识队列优先级,可以传入两种类型的枚举作为参数。通常传入0,代表队列的优先级是默认优先级,处于高优先级和低优先级之间。
  • flags:预留字段,传入任何非0得值都可能导致返回NULL。

创建队列

dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
  • label: 队列的名称,调试的时候可以区分其他的队列
  • attr: 队列的属性,dispatchqueueattrt类型。用以标识队列串行,并行,以及优先级等信息

dispatchbarrier

使用dispatchbarrier将任务加入到并行队列之后,任务会在前面任务全部执行完成之后执行,任务执行过程中,其他任务无法执行,直到barrier任务执行完成。

技术分享

4个API

void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);

并发编程面临的挑战

(以下来自Objc中国)


使用并发编程会带来许多陷阱。只要一旦你做的事情超过了最基本的情况,对于并发执行的多任务之间的相互影响的不同状态的监视就会变得异常困难。

1.资源共享

并发编程的许多问题的根源就是在多个线程当中访问共享资源。 资源可以是一个属性,通用的内存,网络设备或者一个文件等等。

以一张图片说明资源共享所引发的问题。

技术分享

在实际的开发中,情况甚至要比上面的情况更加复杂,因为现代 CPU 为了优化目的,往往会改变向内存读写数据的顺序(乱序执行)。

2.互斥锁

互斥访问的意思就是同一时刻,只允许一个线程访问某个特定资源。为了保证这一点,每个希望访问共享资源的线程,首先需要获得一个共享资源的互斥锁,一旦某个线程对资源完成了操作,就释放掉这个互斥锁,这样别的线程就有机会访问该共享资源了。

技术分享

虽然出于安全性的考虑,Objective-C将所有的属性都默认设置为原子操作,是的所有的属性都能支持互斥锁。(atomic,在之后的关于多线程的学习中会深入讨论原子操作)。但是要知道加锁的过程是需要昂贵的代价的。

  • 资源上的枷锁会引发一定的性能代价。
  • 获取锁和释放锁本身也需要没有竟态条件。
  • 当某个线程因为等待其他线程释放锁的过程当中会处于睡眠状态,并且当其他线程用完锁之后,睡眠的线程会接收到通知,这些过程都是非常昂贵且复杂的。

所以,在编程当中,不需要多线程的情况下,并且,即使是在多线程的情况下,如果能确认不会引发资源共享的情况发生,也尽量不要使用原子操作。

3.死锁

互斥锁解决了竞态条件的问题,但很不幸同时这也引入了一些其他问题,其中一个就是死锁。当多个线程在相互等待着对方的结束时,就会发生死锁,这时程序可能会被卡住。

技术分享

4.资源饥饿

一个资源在两个或者两个以上的线程当中相互使用,第三方线程始终得不到这部分资源。 这部分待之后理解

5.优先级反转

低优先级的任务先获取到共享资源的锁,高优先级的任务需要等待低优先级的把锁解开后才能去访问共享资源,如果在这个等待过程当中,有一个中优先级的任务不需要块共享资源,那么他就有可能会枪战低优先级的任务而先于高优先级的任务被执行。由于低优先级的任务持有锁,然而他又被中优先级的任务阻塞无法释放锁,高优先级的任务就一直无法被执行,从而能导致优先级翻转。

所以,我们说尽量不要使用不同的优先级,使用的优先级越多,情况就越不好控制,可能出现的问题就越多。


多线程一

标签:

原文地址:http://blog.csdn.net/qingbaobei_loveyou/article/details/51220590

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!