线程进程与协程
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连带销毁功能一样的)。但一旦进程崩溃,整个连带的所有线程全部崩溃。因为线程是需要依赖于进程的内存空间的,你内存空间都没了,线程当然就没什么用了。
共同点:
在并发的层面上:多个进程可以同时进行,一个进程中的多个线程也可以同时进行。
联系:
处理机(中央处理器+主存储器+输入输出设备)分配资源给进程,即真正运行在处理机上的是进程。
线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。线程是指进程内的一个执行单元,也是进程内的可调度实体。
协程是什么?
协程是更轻量级的线程,是一种特殊的函数。根据上述可以得知一个进程可以拥有多个线程,那么一个线程也可以拥有多个协程。线程和进程由系统内核控制,但是协程这个函数是由用户来控制的。
协程对于线程的好处
每个线程拥有独立的栈资源,来回切换时还是会造成上下文切换的资源浪费。而协程是由用户控制,无需进行用态和内核态之间的上下文切换,也就节省了大量的资源,并且极大的提升了性能。
一个线程内可以执行多个协程,并且多个协程之间不用像线程一样加锁来保证资源共享的安全性。因为协程在线程中是以串行的方式进行的。不可能出现多个协程抢夺一个资源的情况出现。
总结:
-
进程是资源分配的最小单位,拥有独立的内存空间。
-
线程是操作系统调度的最小单位,不拥有独立的内存空间,依赖于进程的堆资源,拥有自己的栈资源。
-
协程是轻量级线程,一种特殊的函数。不被操作系统所调用,被用户所操控,可以自由的决定调度与终止的时机。
-
协程不用像线程一样进行用户态和内核态的上下文切换,节省了大量的资源。
参考文献:
进程、线程、协程是什么?有什么区别和联系? - 掘金 (juejin.cn)
进程、线程和协程的关系与区别 - 掘金 (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()
以上是关于线程进程与协程的主要内容,如果未能解决你的问题,请参考以下文章