• 进程线程和协程


    1、概念

    从计算机硬件角度:

    计算机的核心是CPU,承担了所有的计算任务。一个CPU,在一个时间切片里只能运行一个程序。

    1.1 进程

    进程:是CPU对程序的一次执行过程、一次执行任务。各个进程有自己的内存空间、数据栈等。操作系统分配内存的基本单位(打开、执行、保存...)

    1.2 线程

    线程:是进程中执行运算的最小单位,是进程中的一个实体。(打开、执行、保存...)

    一个程序至少有一个进程,一个进程至少有一个线程。

    操作系统分配CPU的基本单位

    1.3 协程

    协程:比线程更小的执行单元,又称微线程,在单线程上执行多个任务,自带CPU上下文

    用函数切换,开销极小。不通过操作系统调度,

    没有进程、线程的切换开销。(gevent,monkey.patchall)

    举例

    我们假设把一个进程比作我们实际生活中的一个拉面馆,负责保持拉面馆运行的服务员就是线程,每个餐桌代表要完成的任务。

    当我们用多线程完成任务时,模式是这样的:每来一桌的客人,就在那张桌子上安排一个服务员,即有多少桌客人就得对应多少个服务员;

    而当我们用协程来完成任务时,模式却有所不同了: 就安排一个服务员,来吃饭得有一个点餐和等菜的过程,当A在点菜,就去B服务,B叫了菜在等待,我就去C,当C也在等菜并且A点菜点完了,赶紧到A来服务… …依次类推。

    从上面的例子可以看出,想要使用协程,那么我们的任务必须有等待。当我们要完成的任务有耗时任务,属于IO密集型任务时,我们使用协程来执行任务会节省很多的资源(一个服务员和多个服务员的区别,并且可以极大的利用到系统的资源。

    1.4 线程安全

    多线程环境中,共享数据同一时间只能有一个线程来操作。

    1.5 原子操作

    原子操作就是不会因为进程并发或者线程并发而导致被中断的操作。

    1.6 并行和并发

    串行:单个CPU核心,按顺序执行

    并行:多个CPU核心,不同的程序就分配给不同的CPU来运行。可以让多个程序同时执行。(多进程)

    并发:单个CPU核心,在一个时间切片里一次只能运行一个程序,如果需要运行多个程序,则串行执行,遇到IO阻塞就切换,即计算机在逻辑上能处理多任务的能力。(多进程,多线程)

    1.7 多进程/多线程

    表示可以同时执行多个任务,进程和线程的调度是由操作系统自动完成。

    进程:每个进程都有自己独立的内存空间,不同进程之间的内存空间不共享。

    线程:一个进程可以有多个线程,所有线程共享进程的内存空间,通讯效率高,切换开销小。共享意味着竞争,导致数据不安全,为了保护内存空间的数据安全,引入"互斥锁",“递归锁”,“升序锁”等。

    Python的多线程:

    GIL:Global Interpreter Lock, 全局解释器锁,线程的执行权限,在Python的进程里只有一个GIL。

    一个线程需要执行任务,必须获取GIL。

    好处:直接杜绝了多个线程访问内存空间的安全问题。

    坏处:Python的多线程不是真正多线程,不能充分利用多核CPU的资源。

    但是,在I/O阻塞的时候,解释器会释放GIL。

    同步,异步,阻塞,非阻塞

    异步,异步本质上是单线程的,因为 IO 操作在很多时候会存在阻塞,异步就是在这种阻塞的时候,通过控制权的交换来实现多任务的。即异步本质上是运行过程中的控制权的交换。最典型的例子就是生产者消费者模型。

    同步,即程序协同进行,遇到阻塞就等待,直到任务完成为止。

    多进程:密集CPU任务,需要充分使用多核CPU资源(服务器,大量的并行计算)的时候,用多进程。 multiprocessing

    缺陷:多个进程之间通信成本高,切换开销大。

    多线程:密集I/O任务(网络I/O,磁盘I/O,数据库I/O)使用多线程合适。

    threading.Thread、multiprocessing.dummy

    缺陷:同一个时间切片只能运行一个线程,不能做到高并行,但是可以做到高并发。

    协程:又称微线程(一种用户态的轻量级线程),在单线程上执行多个任务,用函数切换,由程序自身控制,开销极小。

    不通过操作系统调度,没有进程、线程的切换开销。

    每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置,不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

    当程序中存在大量不需要CPU的操作时(IO),遇到IO操作自动切换到其它协程。

    greenlet, gevent,monkey.patchall,yield,async

    多线程请求返回是无序的,哪个线程有数据返回就处理哪个线程,而协程返回的数据是有序的。

    因为协程是一个线程执行,所以想要利用多核CPU,最简单的方法是多进程+协程,这样既充分利用多核,又充分发挥协程的高效率。

    缺陷:单线程执行,处理密集CPU和本地磁盘IO的时候,性能较低。处理网络I/O性能还是比较高.

    Python的GIL只能保证原子操作的线程安全,因此在多线程编程时我们需要通过加锁来保证线程安全。

    最简单的锁是互斥锁(同步锁),互斥锁是用来解决IO密集型场景产生的计算错误,即目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据。

    递归锁:就是在一个大锁中再包含子锁

    升序锁:两个线程想获取到的锁,都被对方线程拿到了,那么我们只需要保证在这两个线程中,获取锁的顺序保持一致就可以了。举个例子,我们有线程thread_a, thread_b, 锁lock_1, lock_2。只要我们规定好了锁的使用顺序,比如先用lock_1,再用lock_2,当线程thread_a获得lock_1时,其他线程如thread_b就无法获得lock_1这个锁,也就无法进行下一步操作(获得lock_2这个锁),也就不会导致互相等待导致的死锁。简言之,解决死锁问题的一种方案是为程序中的每一个锁分配一个唯一的id,然后只允许按照升序规则来使用多个锁,这个规则使用上下文管理器 是非常容易实现的。

  • 相关阅读:
    【像程序员一样思考】读书笔记4
    MFC ListControl 与 Excel 数据的导入导出
    OpenCV中findContours函数的使用
    十大算法
    qsort对二维数组的排序
    【像程序员一样思考】读书笔记3
    【像程序员一样思考】 读书笔记2
    【像程序员一样思考】 读书笔记1
    代码混淆
    布局优化
  • 原文地址:https://www.cnblogs.com/jcb9426986/p/14282348.html
Copyright © 2020-2023  润新知