iOS 整理-线程篇

什么是进程和线程
进程和线程之间的关系
什么是任务和队列
多线程中同步异步,串行并行
iOS中多线程的区别:NSThread、NSOperation、GCD
iOS中死锁的必要条件
iOS中几种锁的区别和使用场景
sleep,join,yield方法的作用和区别
dispatch_once 的底层实现

什么是进程和线程

进程:进程就是一段程序的执行过程,在操作系统中,进程既是基本的分配单元,也是基本的执行单元。每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内,拥有独立运行所需的全部资源。

线程: 通常在一个进程中可以包含若干个线程,一个进程中至少有一个线程,也是线程的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。

进程和线程之间的关系
  • 线程是进程的执行单元,进程的所有任务都在线程中执行
  • 线程是 CPU 分配资源和调度的最小单位
  • 一个程序可以对应多个进程(多进程),一个进程中可有多个线程,但至少要有一条线程
  • 同一个进程内的线程共享进程资源
什么是任务和队列

任务:执行操作,执行代码;在多线程中,任务按照进行的方式分为:同步执行和异步执行。
队列:指执行任务的等待队列,即用来存放任务的队列,采用 FIFO(先进先出)的原则。在多线程中,队列分为:串行队列和并行队列

多线程中同步异步,串行并行
  • 同步:所有任务在同一个线程执行,不具有开辟新线程能力,按照先进先出原则,等一个任务执行结束,在执行下一个任务。
  • 异步:任务会在不同线程执行,开辟新的线程,不同线程中的任务没有关联。
  • 串行:所有任务在同一个线程(不一定主线程)执行,在同一时间只执行一个任务,执行完后再执行下一个任务。
  • 并行:允许同时执行多个任务,只有在不同线程才有效,即在多个线程中。

在多线程中,任务和队列是组合使用的:

  • 同步串行: 同一个线程,串行执行任务
  • 异步串行:开辟一个线程,串行执行任务
  • 同步并行:同一个线程,无法并行,因此也是串行执行
  • 异步串行:多个线程,多个任务可以同时执行,优化等待时间。
iOS中多线程的区别:NSThread、NSOperation、GCD
  • NSThread:最轻量级,使用上灵活,但需要手动管理线程的生命周期、线程同步和线程加锁等,开销较大。因此不建议用来管理线程,可以作为简单的开启新线程的操作。

  • GCD:基于C语言的,可替代NSThread, NSOperation的高效和强大的技术,在多线程的管理和运用上使用起来非常的灵活,不仅对线程的管理,和复杂线程的需求都能派上用场。

  • NSoperation:对GCD的封装,面向对象的。不需要关心线程管理,数据同步的事情,控制并发数,可以把精力放在自己需要执行的操作上。NSOpertation可以建立依赖关系,也可以继承NSOpertation抽象类封装,KVO等功能,但是效率没有GCD高。

iOS中死锁的必要条件

死锁的四个必要条件:

(1)互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源

(2)请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放

(3)不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放

(4)环路等待(循环等待)条件:是指进程发生死锁后,必然存在一个进程--资源之间的环形链,通俗讲就是你等我的资源,我等你的资源,大家一直等。

  • 在GCD多线程中造成死锁的满足两个条件:
    1、在同一个线程中,出现多个同步任务,并且任务之间存在嵌套关系,且存在嵌套关系的任务,是在同一个队列中;
    2、当有两个或两个以上的线程都获得对方的资源,但彼此有不肯放开,处于僵持状态。
// 在主线程中执行同步线程
// 主队列会陷入互相等待的情况
dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"线程:1>>>%@",[NSThread currentThread]);
        });
iOS中几种锁的区别和使用场景
  • @synchronized 关键字加锁,为关键字的加锁

  • NSLock 对象锁,最基本的互斥锁,遵循了 NSLocking 协议,通过 lock 和 unlock 来进行锁定和解锁。同一个线程不能多次调用 lock方法,会造成死锁。

  • NSRecursiveLock 递归锁,专门为递归调用,可以在同一线程多次获得,但是同时得有相同次数的解锁,否则会死锁。(解决NSLock在递归遍历深层却无法解锁的问题。)

  • NSConditionLock 条件锁,遵循了 NSLocking 协议,与NSLock类似,但多了一个条件因素。
    1、只有 condition 参数与初始化时候的 condition 相等,lock 才能正确进行加锁操作。
    2、unlockWithCondition: 并不是当 condition 符合条件时才解锁,而是解锁之后,修改 condition 的值。

  • pthread_mutex 互斥锁(C语言)主要在pThread多线程中使用到的加锁。

  • dispatch_semaphore 信号量实现加锁(GCD)

  • OSSpinLock 自旋锁,和互斥锁类似,都是为了保证线程安全的锁。但二者的区别是不一样的,对于互斥锁,当一个线程获得这个锁之后,其他想要获得此锁的线程将会被阻塞,直到该锁被释放。
    但自选锁不一样,当一个线程获得锁之后,其他线程将会一直循环在哪里查看是否该锁被释放。因此可以说,只要解锁就能立刻知道,不需要等到访问的时候。
    缺点:一直处于循环状态,消耗性能,仅适用于短时间持锁。

sleep,join,yield方法的作用和区别

sleep:是一个Thread类的静态方法,让调用它的线程休眠指定的时间,可用于暂停线程,但不会把锁让给其他线程,时间一到,线程会继续执行。

join:线程有严格的先后顺序,调用它的线程需要执行完以后其他线程才会跟着执行。

yield是暂停当前正在执行的线程对象,把时间让给其他线程,常用在死循环中。

dispatch_once 的底层实现
  void dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func) {
    struct _dispatch_once_waiter_s * volatile *vval = (struct _dispatch_once_waiter_s**)val;
    struct _dispatch_once_waiter_s dow = { NULL, 0 };
    struct _dispatch_once_waiter_s *tail, *tmp;
    _dispatch_thread_semaphore_t sema;
 
    if (dispatch_atomic_cmpxchg(vval, NULL, &dow)) {
        _dispatch_client_callout(ctxt, func);
        tmp = dispatch_atomic_xchg(vval, DISPATCH_ONCE_DONE);
        tail = &dow;
        while (tail != tmp) {
            while (!tmp->dow_next) {
                _dispatch_hardware_pause();
            }
            sema = tmp->dow_sema;
            tmp = (struct _dispatch_once_waiter_s*)tmp->dow_next;
            _dispatch_thread_semaphore_signal(sema);
        }
    } else {
        dow.dow_sema = _dispatch_get_thread_semaphore();
        for (;;) {
            tmp = *vval;
            if (tmp == DISPATCH_ONCE_DONE) {
                break;
            }
            dispatch_atomic_store_barrier();
            if (dispatch_atomic_cmpxchg(vval, tmp, &dow)) {
                dow.dow_next = tmp;
                _dispatch_thread_semaphore_wait(dow.dow_sema);
            }
        }
        _dispatch_put_thread_semaphore(dow.dow_sema);
    }
}

第一次调用: 此时外部传进来的 onceToken 还是空指针,所以 vval 为 NULL,if 判断成立。首先执行 block,然后让将 vval 的值设为 DISPATCH_ONCE_DONE 表示任务已经完成,同时用 tmp 保存先前的 vval。此时,dow 也为空,因此 while 判断不成立,代码执行结束。

同一线程第二次调用: 由于 vval 已经变成了 DISPATCH_ONCE_DONE,因此 if 判断不成立,进入 else 分支的 for 循环。由于 tmp 就是 DISPATCH_ONCE_DONE,所以循环退出,没有做任何事。

多个线程同时调用: 由于 if 判断中是一个原子性操作,所以必然只有一个线程能进入 if 分支,其他的进入 else 分支。由于其他线程在调用函数时,vval 还不是 DISPATCH_ONCE_DONE,所以进入到 for 循环的后半部分。这里构造了一个链表,链表的每个节点上都调用了信号量的 wait 方法并阻塞,而在 if 分支中,则会依次遍历所有的节点并调用 signal 方法,唤醒所有等待中的信号量。

你可能感兴趣的:(iOS 整理-线程篇)