iOS多线程

1、什么是多线程

多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。

原理:
同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)
多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)
如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
注意:多线程并发,并不是cpu在同一时刻同时执行多个任务,只是CPU调度足够快,造成的假象。
优点:
能适当提高程序的执行效率
能适当提高资源利用率(CPU、内存利用率)
缺点:
开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
线程越多,CPU在调度线程上的开销就越大

2、多线程多种实现方式

NSThread

(1)使用NSThread对象建立一个线程非常方便
(2)但是!要使用NSThread管理多个线程非常困难,不推荐使用
(3)技巧!使用[NSThread currentThread]获得任务所在线程,适用于这三种技术
(4)使线程休眠3秒:[NSThread sleepForTimeInterval:0.3f];

优点:NSThread 比其他两个轻量级,使用简单
缺点:需要自己管理线程的生命周期、线程同步、加锁、睡眠以及唤醒等。线程同步对数据的加锁会有一定的系统开销

GCD( Grand Central Dispatch)

GCD 是iOS 4.0以后才出现的并发技术
使用方式:将任务添加到队列(串行/并行(全局)),指定执行任务的方法,(同步(阻塞)/异步 )
拿到主队列:dispatch_get_main_queu()
NSOperation无法做到的:1.一次性执行,2.延迟执行,3.调度组(op实现要复杂的多 )

(1)是基于C语言的底层API
(2)用Block定义任务,使用起来非常灵活便捷
(3)提供了更多的控制能力以及操作队列中所不能使用的底层函数

(1)任务
我们需要将要执行的代码用block封装好,然后将任务添加到队列并指定任务的执行方式,等待CPU从队列中取出任务放到对应的线程中执行。

  • queue:队列
  • block:任务
    // 1.用同步的方式执行任务
    dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

// 2.用异步的方式执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

// 3.GCD中还有个用来执行任务的函数
// 在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

(2)队列

串行队列:串行队列一次只调度一个任务,一个任务完成后再调度下一个任务。
// 1.使用dispatch_queue_create函数创建串行队列
// 创建串行队列(队列类型传递NULL或者DISPATCH_QUEUE_SERIAL)
dispatch_queue_t queue = dispatch_queue_create("queue", NULL);

// 2.使用dispatch_get_main_queue()获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
注意:主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行。

GCD的其他用法

(1)延时执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2秒后异步执行这里的代码...
});
(2)一次性执行
// 使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});
(3)调度组(队列组)
需要在多个耗时操作执行完毕之后,再统一做后续处理。

//创建调度组
dispatch_group_t group = dispatch_group_create();
//将调度组添加到队列,执行 block 任务
dispatch_group_async(group, queue, block);
//当调度组中的所有任务执行结束后,获得通知,统一做后续操作
dispatch_group_notify(group, dispatch_get_main_queue(), block);

GCD里面有哪几种Queue

  • 串行和并行,串行Queue里有一个特殊的队列,为main dispatch queue,为应用程序的主线程。
  • Serial:又叫private dispatch queues,同时只执行一个任务。Serial queue常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然各自是同步,但serial queue之间是并发执行。
  • Concurrent:又叫global dispatch queue,可以并发的执行多个任务,但执行完成顺序是随机的。

NSOperation/NSOperationQueue:

使用方式:将操作(异步执行)添加到队列(并发/全局)
拿到主队列:[NSOperationQueue mainQueue] 主队列,任务添加到主队列就会在主线程执行
提供了GCD不好实现的:1.最大并发数,2.暂停和继续,3.取消所有任务,4.依赖关系

(1)是使用GCD实现的一套Objective-C的API
(2)是面向对象的线程技术
(3)提供了一些在GCD中不容易实现的特性,如:限制最大并发数量、操作之间的依赖关系

NSOperation是基于GCD的面向对象的使用OC语言的封装。相比GCD,NSOperation的使用更加简单。NSOperation是一个抽象类,也就是说它并不能直接使用,而是应该使用它的子类。使用它的子类的方法有三种,使用苹果为我们提供的两个子类 NSInvocationOperation, NSBlockOperation和自定义继承自NSOperation的子类。

NSOperation的使用常常是配合NSOperationQueue来进行的。只要是使用NSOperation的子类创建的实例就能添加到NSOperationQueue操作队列之中,一旦添加到队列,操作就会自动异步执行(注意是异步)。如果没有添加到队列,而是使用start方法,则会在当前线程执行。

回到主线程更新UI。
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"更新UI-----%@",[NSThread currentThread]);
}];

NSOperationQueue支持的高级操作有:队列的挂起,队列的取消,添加操作的依赖关系和设置最大并发数。

(1)最大并发数
self.opQueue.maxConcurrentOperationCount = 2;
(2)线程的挂起
self.opQueue.suspended = !self.opQueue.isSuspended;
(3)取消队列里的所有操作
[self.opQueue cancelAllOperations];
(4)依赖关系
[op2 addDependency:op1];

NSThread、GCD和NSOperationQueue的比较 (https://juejin.im/entry/58aacac08ac247006e625b8c)

  • NSThread是对POSIX thread的封装,也就是pthread,它可以直接接触线程,控制线程,如果不加控制,很容易开辟出很多线程,消耗很多资源;
  • GCD和NSOperationQueue都是基于线程池的,不直接接触线程,开发者只需要把task扔给线程池,让线程池去分配执行就好了。其中NSOperationQueue是对GCD的更高一层的封装,可以取消Operation的执行。但写起来相较GCD没有那么直观可靠。GCD使用起来很方便,通过Block回调,但需要注意retain cycle现象。

3、线程同步

信号量、锁、代码块、原子变量

使用atomic一定是线程安全的吗?

  • atomic与nonatomic是属性的修饰符,atomic的线程安全是系统基于setter、getter方法做@synchronized(self),但这种做法是需要消耗很多资源的,一般iOS开发我们使用nonatomic。

  • atomic所说的线程安全只是保证了getter和setter存取方法的线程安全,并不能保证整个对象是线程安全的.(当使用atomic时,虽然对属性的读和写是原子性的,但是仍然可能出现线程错误:当线程A进行写操作,这时其他线程的读或者写操作会因为该操作而等待。当A线程的写操作结束后,B线程进行写操作,然后当A线程需要读操作时,却获得了在B线程中的值,这就破坏了线程安全,如果有线程C在A线程读操作前release了该属性,那么还会导致程序崩溃。所以仅仅使用atomic并不会使得线程安全,我们还要为线程添加lock来确保线程的安全。

背后的线程模型是什么样的?

  • 线程模型是苹果负责处理维护的线程池,所有的队列都只能从这个池子里拿线程,开发者不直接接触线程,而是把任务扔给线程池负责处理。

为什么iOS里,UI必须在主线程更新? (http://blog.csdn.net/csdn_liuzongyi/article/details/73200815)

1)UIKit这种级别的框架,确保线程安全成本非常大的。因而苹果并没有将UIKit完全做成线程安全的。
2)其次,如果在非主线程操作UI的话,其实不一定会发生崩溃,但是可能会延迟UI更新。因为苹果会将非主线程内的UI操作延迟到该子线程释放后,再从主线程里调用更新的函数栈。这时机是很不确定的。

4、常用锁有哪几种?

  • NSLock
    @synchronized(隐式异常处理)
    NSRecursiveLock
    NSConditionLock
    NSDistributedLock
    信号量(生产者消费者模式)pthread_mutex_t、dispatch_semaphore_t
    GCD的同步锁:串行队列+同步\并行队列+dispatch_barrier_async

各种锁的使用

  • https://www.zybuluo.com/MicroCai/note/64272
    http://www.tanhao.me/pieces/616.html/

NSRecursiveLock是什么样的锁?
NSRecursiveLock是一个递归锁,主要用于循环或者多线程多次加锁的情况。NSLock只允许一次加锁,若加锁后未解锁,则下次加锁不会进行,会一直等待解锁操作完成。如下代码就是经典的使用NSLock在多条线程加锁中造成死锁的现象。这种情况下,需要用NSRecursiveLock替代NSLock。

NSLock *lock = [[NSLock alloc] init];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    static void (^RecursiveMethod)(int);
    RecursiveBlock = ^(int num) {
        [lock lock];
        if (value > 0) {
            NSLog(@"number = %d", num);
            sleep(2);//做一次休眠,让第二次lock先于第一次unlock执行
            RecursiveBlock(num - 1);
        }
        [lock unlock];
    };
    RecursiveBlock(10);
});

实现一个变量的读写锁

场景:
同一时间,只能有1个线程进行写的操作
同一时间,允许有多个线程进行读的操作
同一时间,不允许既有写的操作,又有读的操作

上面的场景就是典型的“多读单写”,经常用于文件等数据的读写操作,iOS中的实现方案有:

1、读写锁:pthread_rwlock
概述:读写锁与互斥量类似,不过读写锁允许更高的并行性。互斥量要么是锁住状态,要么是不加锁状态,而且一次只有一个线程对其加锁。读写锁可以有三种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可用同时占有读模式的读写锁。读写锁也叫做共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的,当它以写模式锁住时,它是以独占模式锁住的。

pthread_rwlock_init 初始化读写锁
pthread_rwlock_rdlock 读取读写锁中的锁
pthread_rwlock_tryrdlock 读取非阻塞读写锁中的锁
pthread_rwlock_wrlock 写入读写锁中的锁
pthread_rwlock_trywrlock 写入非阻塞读写锁中的锁
pthread_rwlock_unlock  解除锁定读写锁
pthread_rwlock_destroy 销毁读写锁

2、dispatch_barrier_async

AFN的实现 , requestHeaderModificationQueue是一个由AFHTTPRequestSerializer创建的并发队列, 使用这个并发队列对一个NSMutableDictionary实现了多读单写的锁.

self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);

image.png

你可能感兴趣的:(iOS多线程)