【3.0】知识点小结(线程相关)

Dream-Z / 2023-06-26 / 原文

【3.0】知识点小结(线程相关)

【一】什么是线程

  • 进程

    • 资源单位
  • 线程

    • 执行单位
  • 将操作系统比喻成大的工厂

    • 进程相当于工厂里面的车间
    • 线程相当于车间里面的流水线

每一个进程必定自带一个线程

进程:资源单位

​ 起一个进程仅仅只是 在内存空间中开辟出一块独立的空间

线程:执行单位

​ 真正被CPU执行的其实是进程里面的线程,线程指的就是代码的执行过程,执行代码中所需要使用到的资源都找所在的进程索要

进程和线程都是虚拟单位,只是为了我们更加方便的描述问题

【二】为何要有线程

  • 开设进程

    • 申请内存空间 -- 耗资源
    • 拷贝代码 - 耗资源
  • 开设线程

    • 一个进程内可以开设多个线程
    • 在一个进程内开设多个线程无需再次申请内存空间及拷贝代码操作
  • 总结线程的优点

    • 减少了资源的消耗
    • 同一个进程下的多个线程资源共享

案例需求:开发一款文本编辑器

​ 获取用户输入的功能

​ 实时展示到屏幕的功能

​ 自动保存数据到硬盘的功能

针对上述功能进程合适还是线程合适?

​ 开三个线程更加合理

【三】开启线程的两种方式

开启线程不需要在main下面执行代码,直接书写即可

但是我们还是习惯性的将启动命令写在main下面

【1】方式一:直接调用

进程与线程的对比


【2】方式二:继承父类


【四】如何实现TCP服务端并发效果

【1】基础版1.0

服务端只能接待一个客户端

  • 客户端

  • 服务端

【2】升级版2.0

封装函数:启用多线程

  • 服务端

  • 客户端

【五】线程对象的 join 方法


【六】同一个进程下的多个线程之间数据是共享的


【七】线程对象属性及其他方法

同一个进程下的进程号相同


current_thread

获取当前进程的名字


active_count

统计当前活跃的线程数

  • 这里统计的线程数 2 个
    • 可能已经有一个子线程已经死了

  • 这里统计的活跃进程数是 3 个
    • 让我们第一个子进程晚一点死

【八】守护线程

【1】主线程死亡,子线程未死亡

主线程结束运行后不会马上结束,而是等待其他非守护子线程结束之后才会结束

如果主线程死亡就代表者主进程也死亡,随之而来的是所有子线程的死亡


【2】主线程死亡,子线程也死亡


【3】迷惑性例子


分析

​ t1 是守护线程,会随着主线程的死亡而死亡

​ 当多线程开启时,主线程运行,开启子线程

​ 再开启主线程

​ 主线程结束后会等待非守护子线程结束,所以需要等待3s,等待func结束运行

​ 所以执行顺序是 子线程1---子线程2---主线程---子线程1结束---子线程2结束

【九】线程的互斥锁

【1】问题

所有子线程都会进行阻塞操作,导致最后的改变只是改了一次


【2】解决办法

在数据发生变化的地方进行加锁处理


【十】GIL全局解释器锁

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple
native threads from executing Python bytecodes at once. This lock is necessary mainly

because CPython’s memory management is not thread-safe. (However, since the GIL
exists, other features have grown to depend on the guarantees that it enforces.)

结论:在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势
  • Python解释器其实有多个版本

    • Cpython
    • Jpython
    • Pypypython
  • 但是普遍使用的都是Cpython解释器

  • Cpython 解释器中 GIL 是一把互斥锁,用来阻止同一个进程下的多个线程的同时进行

    • 同一个进程下的多个线程无法利用这一优势?
      • Python的多线程是不是一点用都没有?
  • 因为在 Cpython 中的内存管理不是线程安全的

    • ps:内存管理(垃圾回收机制)
      • 应用计数

      • 标记清除

      • 分代回收

1.GIL 不是python的特点而是Cpython解释器的特点

2.GIL 保证解释器级别的数据的安全

3.GIL会导致同一个进程下的多个线程的无法同时进行即无法利用多核优势

4.针对不同的数据还是需要加不同的锁处理

5.解释型语言的通病:同一个进程下的多个线程无法利用多核优势

【十一】GIL锁与普通互斥锁的区别

【1】普通版 1.0

当睡了 0.1s 后

所有线程都去抢那把 GIL 锁住的数据,当所有子线程都抢到后再去修改数据就变成了 99


【2】升级版 2.0

谁先抢到谁就先处理数据


【3】终极版 3.0

自动加锁并解锁

子线程启动 , 后先去抢 GIL 锁 , 进入 IO 自动释放 GIL 锁 , 但是自己加的锁还没解开 ,其他线程资源能抢到 GIL 锁,但是抢不到互斥锁

最终 GIL 回到 互斥锁的那个进程上,处理数据


【十二】同一个进程下的多线程无法利用多核优势,是不是就没用了

【1】多线程是否有用要看情况

  • 单核
    • 四个任务(IO密集型/计算密集型)
  • 多核
    • 四个任务(IO密集型/计算密集型)

【2】计算密集型

一直处在计算运行中

  • 每个任务都需要 10s

    • 单核

      • 多进程:额外消耗资源

      • 多线程:减少开销

    • 多核

      • 多进程:总耗时 10s
      • 多线程:总耗时 40s+

【3】IO密集型

存在多个 IO 阻塞切换操作

  • 每个任务都需要 10s
    • 多核
      • 多进程:相对浪费资源
      • 多线程:更加节省资源

【4】小结

  • 多线程和多进程都有各自的优势

  • 通常项目都是多进程下面开多进程

    • 这样既可以利用多核又可以节省资源消耗