iOS 整理-线程篇

2020-02-25  本文已影响0人  凉秋落尘

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

什么是进程和线程

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

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

进程和线程之间的关系
什么是任务和队列

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

多线程中同步异步,串行并行

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

iOS中多线程的区别:NSThread、NSOperation、GCD
iOS中死锁的必要条件

死锁的四个必要条件:

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

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

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

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

// 在主线程中执行同步线程
// 主队列会陷入互相等待的情况
dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"线程:1>>>%@",[NSThread currentThread]);
        });
iOS中几种锁的区别和使用场景
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 方法,唤醒所有等待中的信号量。

上一篇 下一篇

猜你喜欢

热点阅读