笔记-多线程底层初探

线程与进程

线程的定义

  • 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行
  • 进程想要执行任务,必须得有线程,进程至少要有一条线程
  • 程序启动会默认开启一条线程,这条线程被称为主线程或UI线程

进程的定义

  • 进程是指在系统中正在运行的一个应用程序
  • 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存

线程与进程的关系

  • 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间
  • 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的
  • 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
  • 线程是处理器调度的基本单位,但是进程不是
  • 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮
  • 进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程

多线程的原理

原理

CPU在单位时间片里快速在各线程之间的切换

意义

优点:

  • 能适当提高程序的执行效率
  • 能适当提高资源的利用率(CPU,内存)
  • 线程上的任务执行完成后,线程会自动销毁

缺点:

  • 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占 512 KB)
  • 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
  • 线程越多,CPU 在调用线程上的开销就越大
  • 程序设计更加复杂,比如线程间的通信、多线程的数据共享

线程的生命周期

  • new 新建一个线程
  • start 开始一个线程,线程进入就绪(runnable)状态
  • CPU调度当前线程,进入运行(Running)状态
  • Running状态之后会出现几种现象
  • 正常执行任务完毕,强制退出;受时间片的影响,切换到其他线程;调用sleep或等待同步锁,从可调度线程池中移除,进入阻塞(Blocked)状态
  • sleep到时,获取到同步锁,重新添加回可调度线程池,再次进入就绪(Runnable)状态

注意:start操作不可重复,当CPU调度当前线程,进入Running状态时,这里存在一个可调度线程池,会进行一系列的判断,如果线程池里有当前线程,会直接执行线程,如果没有,则判断当前线程池的大小是否小于核心线程池的大小,如果小于,则创建一个新的线程来执行;如果大于,则等待被加入到队列等,具体可以参考下面的线程池工作原理。

具体代码看下线程的生命周期:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 根据状态来改变 - 线程
    if (self.t == nil) {
        // new 新建
        self.t = [[NSThread alloc] initWithTarget:self.p selector:@selector(testThreadStatus) object:@100];
        // 2. 启动线程start - runnable
        [self.t start];
        self.t.name = @"学习线程";
    }
}
 
 - (void)testThreadStatus {
    // running
    for (int i = 0; i<10; i++) {
        // blocked
        if (i == 2) {
            sleep(1);
        }
    }
    [self.t cancel];
}
复制代码

流程图:

线程池工作原理

在线程的生命周期中,需要考虑到当前的可调度线程池。 首先需要判断当前线程池的大小是否小于核心线程池大小,如果是小于,则直接创建线程去执行任务;如果是大于,则没有能力开辟新的线程去执行任务,只能依赖现有的线程,则需要判断工作队列是否已经满了,如果没有满,则将任务push到队列,执行任务;如果满了,判断线程池里的线程是否都工作,如果没有,则利用线程去执行任务;如果都在工作,则进入饱和策略。

饱和策略:

  • Abort策略(中止策略)默认策略,新任务提交时直接抛出未检查的异常RejectedExecutionException,该异常可由调用者捕获
  • Discard策略(抛弃任务)新提交的任务被抛弃
  • DiscardOldest策略(抛弃最旧的)队列的是“队头”的任务,然后尝试提交新的任务。(不适合工作队列为优先队列场景)
  • CallerRuns策略(调用者运行)为调节机制,既不抛弃任务也不抛出异常,而是将某些任务回退到调用者。不会在线程池的线程中执行新的任务,而是在调用exector的线程中运行新的任务

保证线程的安全,提高性能,具体可参考iOS的锁

线程和runloop的关系

  • runloop与线程是一一对应的,一个runloop对应一个核心的线程,为什么说是核心的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里
  • runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务
  • runloop在第一次获取时被创建,在线程结束时被销毁
  • 对于主线程来说,runloop在程序一启动就默认创建好了
  • 对于子线程来说,runloop是懒加载的,只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调

转载于:https://juejin.im/post/5c87c596f265da2dab180aac

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值