线程进程与协程

Posted 捶捶自己

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程进程与协程相关的知识,希望对你有一定的参考价值。

进程、线程、协程

进程是什么?

在一台计算机中,我们编写的代码只是一个存储在硬盘的静态文件,通过编译后就会生成二进制可执行文件,当我们运行这个可执行文件后,它会被装载到内存中,接着 CPU 会执行程序中的每一条指令。操作系统会在内存空间形成一个独立的内存体。这个内存体具有独立的内存空间地址和独立的堆空间。那么这个运行中的程序,就被称为「进程」(Process)。它所依赖的上级单位是操作系统。

操作系统会以进程为单位,分配系统资源(如:CPU时间片、内存资源)。进程是资源调分配的最小单位。

进程的状态

在进行CPU执行程序的文件读取加载过程中可能会利用到DMA操作器进行操作。当CPU进行等待资源拷贝的过程中,它可以去执行一些其他任务。等待这个资源读取完毕后会向CPU发送中断指令,CPU收到之后就会继续程序的启动。

其实这是一种串行的思想,CPU会分配给每个进程不同的执行时间片。

因此进程会有 -> 运行-暂停-运行 三个状态。

一个完整的进程状态的变迁如下图:

进程之间的转换需要进行上下文切换,那么什么是上下文切换呢?

各个进程之间是共享 CPU 资源的,在不同的时候进程之间需要切换,让不同的进程可以在 CPU 执行,那么这个一个进程切换到另一个进程运行,称为进程的上下文切换

在详细说进程上下文切换前,我们先来看看 CPU 上下文切换

大多数操作系统都是多任务,通常支持大于 CPU 数量的任务同时运行。实际上,这些任务并不是同时运行的,只是因为系统在很短的时间内,让各个任务分别在 CPU 运行,于是就造成同时运行的错觉。

任务是交给 CPU 运行的,那么在每个任务运行前,CPU 需要知道任务从哪里加载,又从哪里开始运行。

所以,操作系统需要事先帮 CPU 设置好 CPU 寄存器和程序计数器

CPU 寄存器是 CPU 内部一个容量小,但是速度极快的内存(缓存)。我举个例子,寄存器像是你的口袋,内存像你的书包,硬盘则是你家里的柜子,如果你的东西存放到口袋,那肯定是比你从书包或家里柜子取出来要快的多。

再来,程序计数器则是用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置。

所以说,CPU 寄存器和程序计数是 CPU 在运行任何任务前,所必须依赖的环境,这些环境就叫做 CPU 上下文

线程是什么?

线程又被称为轻量级进程,是操作系统(CPU)调度执行的基本单位。

线程上下文切换的是什么?

这还得看线程是不是属于同一个进程:

  • 当两个线程不是属于同一个进程,则切换的过程就跟进程上下文切换一样;
  • 当两个线程是属于同一个进程,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据

进程和线程的区别与联系?

先说区别:

进程拥有自己的内存地址。维护的是静态资源如堆空间、文件句柄、文件标识符等。是系统资源分配的基本单位。

线程没有自己的内存地址,他依靠于进程的内存地址。线程只独享必不可少的资源,如寄存器和栈,维护的是动态资源如栈空间,调度相关的控制信息、控制信号等。是操作系统调用执行的基本单位。

线程同样具有就绪、阻塞、执行三种基本状态,同样具有状态之间的转换关系,线程能减少并发执行的时间和空间开销;

进程是资源的拥有者,而线程是资源的调度者。线程不会拥有资源,但是线程可以调度属于进程的资源。

进程的系统开销更大:

在创建或销毁一个进程时,系统会去为他分配内存空间或者回收内存空间。

因为进程占用的系统资源也多,进行进程之间的上下文切换往往需要fork所有的资源。

而作为轻量级进程的线程系统开销就明显要比进程小得多。拥有共享内存空间的它只需要fork自己的一份栈空间等轻量资源。

进程更加的稳定:

因为进程之间的空间都是独立的,互不影响的。一旦一个进程崩溃,也不会影响到其他进程的正常运行。

但一个进程可以拥有多个线程,线程共享的是进程的内存空间。虽然当一个线程崩溃的时候一般不会连带进程崩溃(除了像javaJVM连带销毁功能一样的)。但一旦进程崩溃,整个连带的所有线程全部崩溃。因为线程是需要依赖于进程的内存空间的,你内存空间都没了,线程当然就没什么用了。

共同点:

在并发的层面上:多个进程可以同时进行,一个进程中的多个线程也可以同时进行。

联系:

处理机(中央处理器+主存储器+输入输出设备)分配资源给进程,即真正运行在处理机上的是进程

线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。线程是指进程内的一个执行单元,也是进程内的可调度实体

协程是什么?

协程是更轻量级的线程,是一种特殊的函数。根据上述可以得知一个进程可以拥有多个线程,那么一个线程也可以拥有多个协程。线程和进程由系统内核控制,但是协程这个函数是由用户来控制的。

协程对于线程的好处

每个线程拥有独立的栈资源,来回切换时还是会造成上下文切换的资源浪费。而协程是由用户控制,无需进行用态和内核态之间的上下文切换,也就节省了大量的资源,并且极大的提升了性能。

一个线程内可以执行多个协程,并且多个协程之间不用像线程一样加锁来保证资源共享的安全性。因为协程在线程中是以串行的方式进行的。不可能出现多个协程抢夺一个资源的情况出现。

总结:

  1. 进程是资源分配的最小单位,拥有独立的内存空间。

  2. 线程是操作系统调度的最小单位,不拥有独立的内存空间,依赖于进程的堆资源,拥有自己的栈资源。

  3. 协程是轻量级线程,一种特殊的函数。不被操作系统所调用,被用户所操控,可以自由的决定调度与终止的时机。

  4. 协程不用像线程一样进行用户态和内核态的上下文切换,节省了大量的资源。

参考文献:

进程、线程、协程是什么?有什么区别和联系? - 掘金 (juejin.cn)

小林coding操作系统进程管理

进程、线程和协程的关系与区别 - 掘金 (juejin.cn)

Event事件与协程



    1.Event事件
        Event事件的作用:
            - 用来控制线程的执行.
            - 由一些线程去控制另一些线程.

    2.进程池与线程池
        1)什么是进程池与线程池?
            进程池与线程池是用来控制当前程序允许创建(进程/线程)的数量.

        2)进程池与线程池的作用:
            保证在硬件允许的范围内创建 (进程/线程) 的数量.

    3.协程
        - 进程: 资源单位
        - 线程: 执行单位
        - 协程: 在单线程下实现并发

        注意: 协程不是操作系统资源,他是程序起的名字,为让单线程能实现并发.

        协程的目的:
            - 操作系统:
                多道技术, 切换 + 保存状态
                    1) 遇到IO
                    2) CPU执行时间过长

            - 协程:
                通过手动模拟操作系统 "多道技术",实现 切换 + 保存状态
                    1)手动实现 遇到IO切换, 欺骗操作系统误以为没有IO操作.
                        - 单线程下遇到IO, 切换 + 保存状态
                        - 单线程下计算密集型, 来回切换 + 保存状态是,反而效率更低

                优点:
                    在IO密集型的情况下, 会提高效率.

                缺点:
                    若在计算密集型的情况下, 来回切换, 反而效率更低.

        4.TCP服务端实现协程
    
# 服务单
from gevent import monkey
monkey.patch_all()
import socket
from gevent import spawn
server = socket.socket()
server.bind((
    '127.0.0.1', 9550
))
server.listen(5)
print('启动服务端。。。')
def working(conn):
    while True:
        try:
            data = conn.recv(1024)
            if len(data) == 0:
                break
            print(data.decode('utf-8'))
            conn.send(data.upper())
        except Exception as e:
            print(e)
            break
    conn.close()
def server2():
    while True:
        conn, addr = server.accept()
        spawn(working, conn)
        # join()
if __name__ == '__main__':
    # 协程,单线程下实现并发
    g1 = spawn(server2)
    g1.join()


# 客户端
import socket
from threading import current_thread, Thread
def client():
    client = socket.socket()

    client.connect(
        ('127.0.0.1', 9550)
    )
    number = 0
    while True:
        data = f'{current_thread().name} {number}'
        client.send(data.encode('utf-8'))
        data = client.recv(1024)
        print(data.decode('utf-8'))
        number += 1

# 模拟500个用户并发去访问服务端
for i in range(500):
    t = Thread(target=client)
    t.start()

以上是关于线程进程与协程的主要内容,如果未能解决你的问题,请参考以下文章

帮你搞懂Python进程,线程与协程

Python档案袋( 进程与协程 )

线程进程与协程

Python:线程进程与协程——线程池

进程线程与协程傻傻分不清?一文带你吃透!

Python:线程进程与协程——Queue模块及源码分析