目录
1.全局解释器锁(GIL, Global Interpreter Lock)
1.全局解释器锁(GIL, Global Interpreter Lock)
GIL和Python语言没关系,他是CPython解释器中的遗留问题。每个线程在执行过程中都需要获取GIL,保证一个进程内同一时刻只有一个线程可以执行代码。
基本行为:
1.当前执行的线程必须要有全局解释器锁
2.当遇到I/O阻塞、或者CPU时间片到都会释放全局解释器锁
2.孤儿进程:
进程:进程是运行中的程序代码,是操作系统资源分配的基本单位。
父进程已经结束任务了,但是子进程还没有运行结束的状态称为孤儿进程。孤儿进程会被pid=1的进程收养。
3.僵尸进程
子进程退出时父进程还处于阻塞状态, 子进程已经结束了但是还占用部分内存资源没有被释放,只能等待父进程结束后才能一起回收内存资源的状态称为僵尸进程。
4.linux下的进程状态
R running 运行和就绪状态
S 可中断的睡眠状态
D 不可中断的睡眠状态
T 暂停状态或者跟踪状态,程序还在内存空间内,只是陷入了长期睡眠的状态
Z zombie 僵尸状态
X 退出状态,即将被销毁的状态
5.多进程
进程之间是相互隔离的状态, 每一个子进程都会从父进程拷贝,生成自己的内存空间。
定义一个多进程的方法:
from multiprocessing import Process, current_process
def task(i):
print(f"current_process name is :{current_process().name}")
print(f"i am task {i}")
# 多进程的执行方式必须在main函数中运行
# 如果当前模块是main就运行多进程
if __name__ == "__main__":
for i in range(4):
p = Process(target=task, args=(i, ))
p.start()
自定义进程类:
from multiprocessing import Process, current_process
class MyProcess(Process):
def __init__(self, num):
# 执行以下父进程的init方法
super().__init__()
self.num = num
# 将进程要执行的函数写到run方法里面
def run(self):
print(f"current_process name is :{current_process().name}")
print(f"i am {self.num}")
if __name__ == "__main__":
for i in range(4):
p1 = MyProcess(i)
p1.start()
6.进程通信:
进程通信:进程通信就是指进程之间的信息交换。
1.管道:管道本质上是一个缓冲区,这个缓冲区与管道文件相关联,对管道文件的操作,被内核转换成对缓冲区的操作。
无名管道:只能在有亲缘关系的进程之间通信。
有名管道:提供一个路径与之关联,即使不存在亲缘关系的进程之间也可以通信,只要知道管道的访问路径就能哦通过这个管道相互通信。
(管道的通信方式简单易实现, 但是效率太低,不适合进程间平方的交换数据,管道只能传输无格式的字节流)
2.消息队列:消息队列本质就是存放在内存中的消息链表,消息本质上是用户自定义的数据结构,进程读取消息后,该消息就会从消息队列中删除。
用户可以向内核中的消息对列中添加信息,也可以读取消息对列的信息,消息队列可以不按顺序随机查询。
(消息队列不适合数据量大的时候使用,频繁的系统调用会消耗很多的cpu时间)
3.内存共享
内存共享:允许不相干的进程将一段物理内存连接到各自的地址空间中,是的这些进程可以访问同一个物理内存。这个物理内存就是共享内存。
(共享内存虽然解决了系统频繁调用的问题,但是可能会引起冲突(资源抢夺))
4.信号量
信号量和共享内存搭配使用,允许最多有多少个进程可以对内存资源进行操作
7.进程互斥锁
互斥锁不要上在全局,多进程会失效,在可能发生资源抢夺的地方上锁,利用完资源就要记得释放锁,不然下一个进程会因为拿不到锁而阻塞。
from multiprocessing import Lock,Process, Queue
num = 0
def add_num(i, lock):
lock.acquire()
global num
num += i
print(f"num is {num}")
lock.release()
if __name__ == '__main__':
print("starting......")
p_list = []
# 创建一个锁实例
lock = Lock()
for i in range(10):
p = Process(target=add_num, args=(i,lock))
p.start()
p_list.append(p)
# 子进程完成再退出
[p.join()for p in p_list]
print("ending..........")
8.进程池
一般情况下我们是通过动态创建子进程(或子线程)来实现并发的,但是这样会村咋一些缺点:
1.动态创建进程比较浪费时间,导致较慢的服务器响应。
2.动态创建子进程通常只用来为一个客户服务,这样导致了系统上产生大量的席位进程(或线程)。进程和线程间的切换消耗大量cpu时间。
3.动态创建的子进程是当前进程的完整映像,当前进程必须谨慎的管理文件描述符资源,否则可能会因为子进程的复制,导致系统的可用资源急剧下降,进而影响服务器性能。
进程池的作用:有效的降低频繁创建和销毁进程所带来的额外开销。
进程池原理:
进程池都是采用与创建技术,在应用启动之初就预先创建一定数量的进程。
应用在运行的过程中,需要时可以从这些进程锁组成的进程池内申请分配一个空闲的进程来执行任务,任务完成后将那个进程返回给进程池,由进程池自行管理。
如果进程池中预先分配的线程已经全部分配完毕,但此时又有新的任务请求,则进程池就会创建新的进程去适应这个请求。
某些时间段应用不需要执行很多的任务,导致了进程池中的线程大多数处于空闲的状态,为了更好的利用系统资源,进程池就会要动态的销毁一部分的空闲进程。
进程需要一个管理者,按照一定的要求去动态的维护进程中的数目。
from multiprocessing import current_process, Pool
import time
def task(i):
print(f"current_process name is :{current_process().name}")
print(f"i am {i}")
if __name__ == '__main__':
# 创建一个进程池,最好进程数和cpu核数一致
# maxtasksperchild 设置进程最多可以处理的任务数,
# 子进程完成了设置的最多任务数,就会退出当前进程
# 进程运行的中间会产生一些中间数据,只能随着进程的释放而释放,如果你要处理的数据量很大
# 就需要重新开启新的进程来存储新的数据,避免资源浪费
p = Pool(processes=4, maxtasksperchild=4)
for i in range(20):
# 进程池接受任务
p.apply_async(func=task, args=(i,))
# 关闭进程池
p.close()
# 阻塞当前上下文环境
p.join()
print("end......process")