定义:程序在计算机中的一次运行。
程序是一个可执行的文件,是静态的占有磁盘。
进程是一个动态的过程描述,占有计算机运行资源,有一定的生命周期(开始运行生成进程,结束运行结束进程)。
系统中如何产生一个进程
【1】用户控件通过调用程序接口或者命令发起请求(应用层发起请求)
【2】操作系统接收用户请求,开始创建进程(操作系统接收请求)
【3】操作系统调配计算机硬件资源,确定进程状态等
【4】操作系统将创建的进程提供给用户使用
进程基本概念
cpu时间片:如果一个进程占有cpu内核则称这个进程在cpu时间片上。
PCB(进程控制块):在内存中开辟的一块控件,用于存放进程的基本信息,也用于系统查找识别进程。
进程ID(PID):系统为每个进程分配的一个大于0的整数,作为进程ID。每个进程ID不重复。(linux查看进程ID:ps -aux)
父子进程:系统中每一个进程(除了系统初始化进程)都有唯一的父进程,可以有0个或多个子进程。父子进程关系便于进程管理,父进程初始化了子进程。
查看进程树:pstree
进程状态:
三态:
< 有较高优先级
N 优先级较低
+前台进程
s 会话组组长
l 有多线程的
进程的运行特征
【1】进程可以使用计算机多核资源
【2】进程是计算机分配资源的最小单位
【3】进程之间的运行互不影响,各自独立
【4】每个进程拥有独立的空间,各自使用自己空间资源
面试要求
os模块的多进程,创建进程,返回创建进程的子进程的进程ID,即pid。
子进程还没结束,但是父进程已经结束了,这时候子进程没有对应的父进程,就称为了孤儿进程,系统会有一个专门回收孤儿进程的父进程,在linux系统开机后就存在。
作业:
from multiprocessing import Process
from time import sleep
import os
def th1():
sleep(2)
print("吃饭")
print(os.getppid(),'----',os.getpid())
def th2():
sleep(2)
print("睡觉")
print(os.getppid(),'----',os.getpid())
def th3():
sleep(2)
print("打豆豆")
print(os.getppid(),'----',os.getpid())
if __name__ == '__main__':
things = [th1,th2,th3]
jobs = []
for th in things:
p = Process(target=th) # 可以用args元组的位置传参,也可以用kwargs用字典键值传参
p.start()
jobs.append(p) # 将进程对象保存
for i in jobs: # 遍历进程列表,回收进程
i.join()
muutiprocessing传参
from multiprocessing import Process
from time import sleep
def worker(sec,name):
for i in range(3):
sleep(sec)
print("I'm %s"%name)
print("i am working")
if __name__ == '__main__':
# args传参
p = Process(target=worker,args=(2,'sunhao'))
# kwargs传参
# p = Process(target=worker,kwargs={'sec':2,'name':'sunhao'})
p.start()
p.join()
管道通信
函数:Pipe()/send()/recv()
fd1,fd2 = Pipe(duplex = True) #默认True,双向通道,fd1写的只能是fd2读,False,单向通道,一个只能读,一个只能写
fd1.send()
fd2.recv()
消息队列:Queue()/q.get()/q.put()
共享内存:Value() Array()
信号量:Semaphore()/acquire()/release()
原理:创建一定数量的进程来处理事件,事件处理完成进程不退出,而是继续处理其他事件,直到所有事件全部处理完毕统一销毁,增加进程的重复利用,降低资源消耗。
就是因为原始的多进程方式会不断的创建进程,关闭进程,造成资源浪费,但是进程池就是创建进程后就创建好的这几个进程来回执行任务,直到任务完成,不会频繁创建和关闭。
Windows下实现进程池的代码,注意,建立进程池必须在main下,否则在Windows下运行不起来,具体原因参照:一吱大懒虫的博客 https://blog.csdn.net/qq_36708806/article/details/79731276
from multiprocessing import Pool
from time import ctime,sleep
# 进程池事件
def worker(msg):
sleep(2)
print(ctime(),'--',msg)
if __name__ == '__main__':
# 创建进程池
pool = Pool(2)
# 向进程池队列添加事件
for i in range(20):
msg = 'sunhao %d'%i
pool.apply_async(func=worker,args=(msg,))
# 关闭进程池
pool.close()
# 回收进程池
pool.join()
【1】 创建线程对象
from threading import Thread
t = Thread()
功能:创建线程对象
参数:target 绑定线程函数
args 元组 给线程函数位置传参
kwargs 字典 给线程函数键值传参
【2】 启动线程
t.start()
【3】 回收线程
t.join([timeout])
示例:各线程拥有相同的内存空间,所以共有的变量a只要一边改变了,另一边也改变。
from threading import *
from time import sleep
import os
a = 1
# 线程函数
def music():
global a
a = 10000
for i in range(3):
sleep(2)
print(os.getpid(),'播放:黄河大合唱')
# 创建线程对象
t = Thread(target=music)
# 开启线程
t.start()
for i in range(4):
sleep(1)
print(os.getpid(),'播放:葫芦娃')
print(a)
# 回收线程
t.join()
传参及如何批量创建线程
from threading import Thread
from time import sleep
def fun(sec,name):
print('线程函数参数')
sleep(sec)
print('%s执行完毕'%name)
jobs = []
# 循环创建线程
for i in range(5):
t = Thread(target=fun,args=(2,),kwargs={'name':'T%d'%i})
jobs.append(t)
t.start()
# 循环关闭线程
for i in jobs:
i.join()
t.name 线程名称
t.setName() 设置线程名称
t.getName() 获取线程名称
t.is_alive() 查看线程是否在生命周期
t.daemon 设置主线程和分支线程的退出关系
t.setDaemon() 设置daemon属性值
t.isDaemon() 查看daemon属性值
daemon为True时主线程退出分支线程也退出。要在start前设置,通常不和join一起使用。
线程池模块:threadpool
"""
thread_attr.py
线程属性示例
"""
from threading import Thread
from time import sleep
def fun():
sleep(3)
print("线程属性示例")
t = Thread(target = fun,name = "Tarena")
t.setDaemon(True) # 主线程退出分支线程也退出
t.start()
t.setName("Tedu")
print("Name:",t.getName()) # 线程名称
print("is alive:",t.is_alive()) # 是否在生命周期
print("Daemon:",t.isDaemon())
# t.join()
同步互斥的方法
死锁:由于上锁造成的程序阻塞
Python线程的gil问题(全局解释器)
什么是GIL:由于Python解释器设计中加入了解释器锁,导致Python解释器同一时刻只能解释执行一个线程,大大降低了线程的执行效率。
导致后果:因为遇到阻塞时线程会主动让出解释器,去解释其他线程。所以,Python多线程在执行多阻塞高延迟IO时可以提升程序效率,其他情况下并不能对效率提升。
GIL问题建议:
尽量使用进程完成无阻塞的并发行为;
不适用c作为解释器(java C#)
结论
在无阻塞状态下,多线程程序和单线程程序执行效率几乎差不多,甚至还不如单线程效率。但是多进程运行相同内容却可以有明显的效率提升。
区别联系:
'''
基于fork的多进程网络并发模型server
'''
import os,signal
from socket import *
# signal处理僵尸进程
signal.signal(signal.SIGCHLD,signal.SIG_IGN)
# 创建TCP套接字
ADDR = ('0.0.0.0',8887)
sk = socket()
sk.bind(ADDR)
# 允许端口复用
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
sk.listen()
# 循环创建子进程进行连接
while True:
conn, addr = sk.accept()
pid = os.fork()
if pid == 0:
sk.close()
while True:
try:
data = conn.recv(1024)
print(data.decode())
conn.send(b'OK')
except Exception as e:
print(e)
os._exit(0)
else:
conn.close()
sk.close()
练习:根据fork多进程并发网络模型思路,完成基于process的多进程并发网络模型
'''
基于processing的多进程并发网络模型
'''
from multiprocessing import Process
import os,signal
from socket import socket
signal.signal(signal.SIGCHLD,signal.SIG_IGN)
ADDR = ('0.0.0.0',8887)
sk = socket()
sk.bind(ADDR)
sk.listen()
def connect_do(conn):
while True:
try:
data = conn.recv(1024)
print(data.decode())
conn.send(b'OK')
except Exception as e:
print(e)
os._exit(0)
while True:
conn, addr = sk.accept()
p = Process(target=connect_do,args=(conn,))
p.daemon = True
p.start()
'''
基于threading的多线程并发网络模型
'''
from threading import Thread
import os,signal
from socket import socket
signal.signal(signal.SIGCHLD,signal.SIG_IGN)
ADDR = ('0.0.0.0',8885)
sk = socket()
sk.bind(ADDR)
sk.listen()
def connect_do(conn):
while True:
try:
data = conn.recv(1024)
print('recv',data.decode())
conn.send(b'OK')
except Exception as e:
print(e)
break
# os._exit(0)
while True:
conn, addr = sk.accept()
p = Thread(target=connect_do,args=(conn,))
p.daemon = True
p.start()
非阻塞IO
定义:通过修改IO属性行为,使原本阻塞的IO变为非阻塞的状态。
IO多路复用
import select
from socket import socket
# 创建TCP套接字
sk = socket()
sk.bind(('0.0.0.0',8888))
sk.listen()
# 初始化rlist读监控事件列表
rlist = [sk,]
while True:
rs,ws,xs = select.select(rlist,[],[])
for i in rs:
if i == sk:
conn,addr = i.accept()
print(addr,'connected')
rlist.append(conn) # 将连接套接字加入到监听list中
else:
data = i.recv(1024)
print(data.decode())
try:
i.send(b'OK')
except Exception as e:
print(e)
i.close()
rlist.remove(i)# 将连接调节自从监听list中删除
* 运算符号(二进制)
* & 按位与---一0则0 (判别属性是否存在)
* | 按位或---一1则1 (增加属性)
* ^ 按位抑或---相同为0,不同为1
* << 向左移动低位补0
* >> 向右移动去掉低位
* 作用:属性中如果都是bool型的,那可以用二进制数字来代替这些属性,规定成一个属性。
p = select.poll()
功能:创建poll对象
返回值:poll对象
p.register(fd,event)
p.unregister(fd)
events = p.poll()
poll_server代码:
from select import *
from socket import socket
# 创建TCP套接字
sk = socket()
sk.bind(('0.0.0.0',8888))
sk.listen()
# 创建poll对象
p = poll()
# 初始化fileno和对应套接字的字典
event_dict ={sk.fileno():sk}
# 将套接字加入到关注中
p.register(sk,POLLIN)
while True:
events = p.poll() #阻塞监控
# 循环取events中的fileno,从event_dict中获取对应的套接字
for f,e in events:
print(f,e)
print(event_dict[f])
# 判断套接字类型,并根据相关套接字类型做不同的操作。
if event_dict[f] == sk:
conn,addr = event_dict[f].accept()
p.register(conn, POLLIN)
event_dict[conn.fileno()] = conn
else:
data = event_dict[f].recv(1024)
print(data.decode())
try:
event_dict[f].send(b'OK')
except Exception as e:
print(e)
event_dict[f].close()
p.unregister(event_dict[f]) # 将断开的连接剔除出监听
from select import *
from socket import socket
sk = socket()
sk.bind(('0.0.0.0', 8881))
sk.listen()
ep = epoll()
event_dict = {sk.fileno(): sk}
ep.register(sk, EPOLLIN)
while True:
events = ep.poll()
print(events)
for f, e in events:
print(f, e)
print(event_dict[f])
if event_dict[f] == sk:
conn, addr = event_dict[f].accept()
ep.register(conn, EPOLLIN)
event_dict[conn.fileno()] = conn
else:
data = event_dict[f].recv(1024)
print(data.decode())
try:
event_dict[f].send(b'OK')
except Exception as e:
print(e)
ep.unregister(event_dict[f])
作业:
asyncio和async/await,由于生态不好,所以不常用
1.greenlet模块