美文网首页
iOS 整理-线程篇

iOS 整理-线程篇

作者: 凉秋落尘 | 来源:发表于2020-02-25 14:43 被阅读0次

    什么是进程和线程
    进程和线程之间的关系
    什么是任务和队列
    多线程中同步异步,串行并行
    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 整理-线程篇

          本文链接:https://www.haomeiwen.com/subject/prfewctx.html