关于进程,线程,多进程和多线程的网络编程
进程线程网络
多任务编程 : 可以有效的利用计算机资源,同时执行多个任务
进程 : 进程就是程序在计算机中一次执行的过程
进程和程序的区别:
程序是一个静态文件的描述,不占计算机的系统资源
进程是一个动态的过程,占有cpu内存等资源,有一定的生命周期
* 同一个程序的不同执行过程即为不同的进程
问题1 什么决定了进程的创建
用户通过应用层程序进行进程的创建申请 ----》
调用操作系统接口进行进程创建 -----》
告知系统内核创建新的进程提供给应用层使用
问题2 进程如何占有CPU
1. 同一个内核同一时刻只能运行一个进程
2. 多个进程对内核资源进行抢占,由操作系统内核进行分配
3. 哪个进程占有计算机内核我们称为该进程占有CPU的时间片
问题3 进程在运行过程中的形态和附带内容
PCB(进程控制块) : 在linux和unix操作系统中,进程创建后会在内存中开辟一块空间存放进程的相关信息,这个空间称之为PCB
PID:在操作系统中进程的唯一标识,是一个大于0的正整数,由系统自动分配
ps -aux 查看进程信息
虚拟内存 : 每个进程占有4G内存地址空间 ,这里的内存指的是虚拟内存
进程状态
三态 :
就绪态 : 进程具备运行条件,等待系统分派处理器以便运行
运行态 : 进程占有cpu处于运行状态
等待态 : 又称为阻塞态或者睡眠态,指进程不具备运行条件,正在等待某些条件的达成
五态:
新建态:创建一个进程的过程,直接表现为执行某个程序或者在程序中创建新的进程
终止态:进程执行结束,完成回收的过程
D 等待态 (不可中断)
S 等待态 (可中断)
T 等待态 (暂停)
R 运行态
Z 僵尸态
+ 前台进程
N 低优先级的进程
< 高优先级的进程
l 有进程链接
s 会话组
进程的优先级
优先级往往决定了一个进程的执行权限和占有系统资源的优先程度
top : 动态查看系统进程运行情况
< > 进行翻页查找
linux 系统中优先级范围 -20 --- 19 -20优先级最高
用户创建进程默认优先级为0
nice : 以指定的优先级运行某个进程
e.g. nice -9 ./while.py 以9 的优先级运行程序
sudo nice --9 ./while.py 以-9的优先级运行程序
renice n PID : 修改一个正在运行的进程的优先级
e.g. renice 8 4277 将4277号进程优先级修改为8
父子进程:在系统中除了初始化进程之外每个进程都是由父进程创建的,每个进程有一个唯一的父进程,可能有多个子进程。
pstree
总结 :
1. 什么是进程
2. 进程和程序的区别
3. 进程的几种状态及相互建的转换
4. 什么是 PCB PID cpu时间片
需求: 两件不相关事情希望同时来做
方案1 : 写两个进程,分别承担不同的事情,各自执行
分析 :1. 两个程序比较麻烦
2. 无法确定两个程序应该在什么时间开始运行
方案2 : 写一个程序,在程序中指定位置调用接口来创建新的进程
通过 os.fork() 函数实现
fork()
功能 : 创建一个新的进程
参数 : 无
返回值 : < 0 表示进程创建失败
== 0 在子进程中fork的返回值为0
> 0 在父进程中fork的返回值大于0
* fork是os模块函数,只能在linux和unix下使用
测试1: 父进程中fork之前的内容,子进程同样也会复制
但是父子进程空间内容的修改不会相互影响
测试2: 父子进程在执行上互不影响,理论上不一定谁先执行
测试3: 子进程虽然复制父进程的空间,但是也有自己独特的特性,比如 自己的PID,进程控制块,进程栈等。父进程中fork的返回值即 为创建的子进程的PID号
进程相关函数
os.getpid()
功能 : 获取当前进程的PID号
os.getppid()
功能 : 获取当前进程父进程的PID号
结束一个进程
os._exit(status)
功能 : 结束一个进程
参数 : 一个数字表示进程的退出状态 通常0表示正常退出进程其他数字表示非正常退出
sys.exit([status])
功能 : 结束一个进程,如果处理了抛出的异常则不结束进程
参数 : 一个数字表示进程的退出状态 同上
还可以是一个字符串,则在进程退出示会打印这个字符串
僵尸进程:子进程先于父进程退出,父进程没有对子进程的退出做相应的处理,此时子进程就会变为僵尸进程。
影响 : 进程退出后,仍有部分信息残留在内存中占用空间,大量的僵尸进程会影响系统运行。所以应该进来避免僵尸进程的产生。
孤儿进程: 父进程先于子进程退出,此时子进程就会变为孤儿进程
影响 : 当一个进程变为孤儿进程,系统会自动的使用一个进程称为孤儿进程的父进程。当孤儿进程退出时,该系统进程会自动回收
处理僵尸进程的方法:
1、让父进程先退出 (不好控制)
2、父进程处理子进程的退出 (阻塞父进程的运行)
os.wait()
功能:等待子进程退出进行处理
参数:无
返回值 : 返回一个包含两个元素的元组,第一个是退出的子进程的PID号,第二个是子进程的退出状态
* wait是一个阻塞函数 即 进程处于等待态,等待某种条件的达成才会继续运行
os.waitpid(pid,option)
功能 : 同wait 处理子进程退出使其不会变成僵尸
参数 : pid -1 表示等待任意子进程退出
>0 表示等待指定进程号的子进程退出
option 0 表示阻塞等待
WNOHANG : 表示非阻塞状态
返回值 : 同wait
wait() ====> waitpid(-1,0)
总结
1.函数的使用 fork getpid getppid _exit exit wait
waitpid
2. 理解什么是僵尸进程什么是孤儿进程即两者产生的过程
3. 知道僵尸进程的危害和两种处理方法
4. 理解进程的创建流程
创建二级子进程处理
在父进程中使用信号处理的方法忽略子进程发来的信号
signal(SIGCHLD,SIG_IGN)
更方便高效的进程创建方法
multiprocessing模块 (标准库模块)
创建的进程的步骤
1. 将要完成的事件封装成一个个函数
2. 使用multiprocessing提供的接口函数创建进程
3. 使新的进程和指定的函数相关联去完成函数中的工作
4. 对进程进行回收处理
* 函数当付给Process 的target变量后函数内容就是对应进程的进程内容,此时函数才有特殊性
* 多个子进程和父进程之间的执行相互不影响
创建子进程
Process() 类
参数: target 指定要绑定的函数
name 给创建的进程起一个名字
args 需要一个元组,给target指定的函数按位置传参
kwargs 需要给一个字典,给target指定的函数按键值 传参
Process() 类 ----- 》 p 进程对象
属性方法
print("进程名称:",p.name)
print("进程PID:",p.pid)
print('进程状态:',p.is_alive())
启动子进程
start()
* start() 时才真正的创建子进程,而不是Process时创建
回收子进程
join([timeout])
timeout : 设置最长阻塞时间,如果超过这个时间还没有子进程退出则不再继续等待
* 内核会帮助应用层记录子进程的退出情况,当使用join函数时内核会及时返回进程状态给应用层进行处理
p.daemon
默认值为False 表示主进程运行结束后 不会影响子进程的运行,知道子进程运行完,进程才会结束
如果设置为True 则主进程运行完毕则所有子进程也不再运行一起退出
* 该属性的设置必须要在start()前
* 该属性的设置并不是 将进程设置为 linux/unix中的守护进程
守护进程: 生命周期长, 与前端控制台无关,后台运行,一般用作系统进程或者自动化运行进程
多进程编程
优点: 可以并行的执行多个任务,提高运行效率
空间独立,数据安全
创建方便
缺点:进程的创建和销毁过程需要消耗较多的计算机资源
在需要频繁的创建和删除较多进程的情况下,资源消耗过多,不适宜使用多进程完成任务
进程池技术
1. 创建进程池 ,在池内放入合适数量的进程
2. 将事件加入进程池的等待队列
3. 使用进程池内的进程不断的执行等待事件
4. 所有事件处理结束后关闭回收进程池
Pool
功能:创建进程池
参数: processes :进程池中进程的数量
apply_async()
功能: 以异步的方式将要执行的事件放入进程池
参数: func : 要执行的函数
args : 给函数按位置传参
kwds : 给函数按照键值传参
返回值 : 返回事件执行后的返回值对象,可以通过调用get() 函数获取事件函数return的内容
apply()
功能 : 按照顺序添加要执行的时间,执行一个再添加一个
close()
功能 : 关闭进程池,使其不能再加入新的事件
join()
功能:阻塞等待进程池将时间都执行结束后回收进程池
map()
功能 : 类似与内建函数map 将第二个参数的迭代对象中的数据逐个带入第一个函数作为参数。只不过兼顾了apply_async功能
pool.map(fun,test) ====>
for i in test:
pool.apply_async(fun,(i,))
创建自己的进程类
1. 继承Process类以获取原有的属性
2. 实现自己需要的功能部分
3. 使用自己的类创建进程即可
进程间通信
管道 消息队列 共享内存 信号 套接字
管道
在内存中开辟一个管道空间,对多个进程可见。在通信形式上形成一种约束。
linux 文件类型
b c d - l s p
目录 普通文件 链接 套接字 管道
multiprocessing ---》 Pipe 函数
Pipe(duplex)
功能 : 创建一个管道
参数 : duplex 默认为 True 表示管道为双向管道
如果设置为False 则表示管道为单向管道
返回值 : 返回两个管道流对象,分别表示管道的两端
如果参数为True(默认) 两个对象均可发送接受
如果为False时 则第一个对象只能接受,第二个对象只能发送
* 向管道发送数据使用send()函数,从管道接受数据使用recv()函数
* recv()函数为阻塞函数,当管道中数据为空的时候会阻塞
* 一次recv() 只能接受一次send()的内容
* send()可以发送字符串数字列表等多种类型数据
消息队列
multiprocessing --- 》 Queue
在内存中开辟一个队列模型,用来存放消息。任何拥有队列对象的进程都可以进行消息的存放和取出
Queue(maxsize = 0)
功能 : 创建一个消息队列对象
参数 : maxsize 默认为0 表示消息队列可以存放的消息有 系统自动分配的空间而定
> 0 正整数 表示队列中最多存放多少条消息
返回值 : 消息队列对象
q.put()
向消息队列中存放一条消息,当消息队列满的时候,会阻塞
存放的消息类型可以使数字列表,字串等
q.full()
判断队列是否为满,如果满则返回True 否则返回False
q.qsize()
查看当前队列中消息数量
q.get()
获取消息,每次获取一条,当消息队列为空是,则阻塞
q.empty()
消息队列为空则返回True 不为空返回False
* put get 中block参数和timeout参数
block 默认为True 表示两个函数都是阻塞函数
如果设置为False则表示不阻塞
timeout 当block设置为True的时候表示超时等待时间
共享内存
在内存中开辟一段内存空间存储数据,每次存储的内容会覆盖上次的内容。由于没有对内存进行格式化的修饰所以存取速度块效率高
from multiprocessing import Value,Array
obj = Value(ctype,obj)
功能 : 开辟共享内存
参数 : ctype 要转变的c的类型
obj 要写入共享内存的初始值
obj.value 属性为获取共享内存中的值
obj = Array(ctype,obj)
功能 : 开辟一个共享内存空间
参数 : 要转换的c的类型
obj : 放入共享内存中的数据,是一个列表,要求列 表中的数据为相同类型数据
如果obj传入一个正数,则表示在共享内存中开辟一个 多大的空间,空间中可以存放的数值类型
信号 :
kill -l 查看系统信号
kill -signame PID 给进程号PID的进程发送signame信号
信号 : 信号名称 含义 默认处理方法
名称 : 系统定义
含义 : 系统定义
处理方式 : 采用默认方式处理 (系统定义 终止 暂停 忽略
忽略信号(当信号没发生过)
采用自定义的方式处理
如何发送信号:
os.kill(pid,sig)
功能 : 向一个进程发送一个信号
参数 : pid :要发送进程的PID号
sig :要发送的信号
signal.alarm(sec)
功能:给自己发送一个时钟信号 (SIGALRM)
参数: sec : 秒数 表示在相应的秒数后发送时钟信号
* 信号是一种异步的进程间通信方式
* alarm 函数在一个进程中如果使用多次,则后面的时钟时间会覆盖前面的时间
信号的处理:
signal.pause()
阻塞等待一个信号的发生
signal.signal(signum,handler)
功能 : 处理信号
参数 : signum : 表示可以处理的信号
handler : 信号的处理方法
默认处理方式 : SIG_DFL
忽略信号 : SIG_IGN
自定义的方式 : function
* signal函数也是一个异步处理信号函数
* SIGSTOP 和 SIGKILL不能被signal函数处理
僵尸进程的信号处理方案 父进程中
signal(SIGCHLD,SIG_IGN)
同步和互斥
临界资源 : 对多个进程或者线程都可见的资源,容易产生争夺,我们将这类资源称为临界资源
临界区 : 对临界资源进行操作的代码区域称之为临界区
解决资源争夺: 同步 或者 互斥
同步 : 同步是一种合作关系,为完成某种任务而建立的多个进程或者线程之间的协调调用,次序等待,传递消息告知资源占用情况
互斥 : 互斥是一种制约关系,当一个进程或者线程进入到临界区后会进行枷锁操作,此时其他进程(线程)无法进如临界区,只有当该
进程(线程)使用后进行解锁,其他人才可以使用。这种技术往往是通过阻塞完成
进程间通信
管道 Pipe recv send
队列 Queue qsize full empty put get
共享内存 Value Array
信号 kill pause alarm signal
同步和互斥
Event() ----> 事件对象e
e.wait() : 产生一种阻塞 ,知道e被set之后才结束阻塞
e.set() : 将e set操作 wait不再阻塞
e.is_set() : 判断e是否是被设置的状态
e.clear() : 将e 变成 没有被设置的状态
进程间同步互斥方法
from multiprocessing import Lock
创建 进程锁对象
lock = Lock()
lock.acquire() 给临界区上锁
lock.release() 给临界区解锁
* 具体实现上 acquire() 为一个条件阻塞函数 当有任意一个进程先进行了acquire操作后,其他进程再企图进行acquire操作时就会阻塞,
直到lock对象被 release 后其他进程才可进行下次acquire操作
with lock: 也可以实现加锁 解锁
线程
* 线程也可以使用计算机的多核资源,也是多任务编程方式之一
* 线程又称为轻量级的进程,在并发上和进程相同但是在创建时消耗资源少
一个进程中可以包含多个线程,这多个线程共享进程的资源
多个线程因为共享进程的资源所以在通信上往往采用全局变量的方法
线程也有自己特有的资源,比如 TID 指令集等
多进程和多线程的区别和联系
1. 多进程和多线程都是多任务编程方式,都可以使用计算机多核
2. 进程的创建要比线程消耗更多的资源
3. 进程空间独立数据更安全,有专门的进程间通信方式进行交互
4. 一个进程包含多个线程,所以线程共享进程资源,没有转门的通信方法,依赖全局量进行通信。往往需要使用同步互斥机制,逻辑需
要考虑更多
5. 进程线程都有自己特有的资源。多个关联任务的时候使用多线程资源消耗更少,如果是多个无关任务也不适于全都使用线程
创建线程
import threading
创建线程函数
threading.Tread()
功能 : 创建线程
参数 : target 线程函数
args 以元组方式给线程函数传参
kwargs 以字典方式给线程函数传参
name 线程名称 (默认Thread-1)
返回值 : 返回线程对象
线程属性和方法
t.start() 启动一个线程
t.is_alive() 查看一个线程的状态
t.name 查看线程的名称
t.join([sec]) 阻塞等待回收线程
daemon 属性
设置 该属性默认为False 主线程执行完毕不会影响其他线程的执行
如果设置为True 则主线程执行完毕其他线程也终止执行
t.setDaemon(True)
或
t.daemon = True
获取daemon属性值
t.isDaemon()
线程间的通信
全局变量进行通信
线程间的同步和互斥
线程 event
创建事件对象
e = threading.Event()
e.wait([timeout]) 如果e被设置则不会阻塞,未被设置则阻塞
timeout为阻塞的超时时间
e.set() 将e变为设置的状态
e.clear() 将e变为未设置的状态
线程锁
lock = threading.Lock() 创建锁对象
lock.acquire() 上锁
lock.release() 解锁
创建自己的线程类
1. 自定义类 继承于 原有线程类 Thread
2. 复写原有的run方法
3. 创建线程对象调用start 的时候会自动执行run
threadpool 线程池第三方模块
sudo pip3 install threadpool
GIL (全局解释器锁)
python -- 》 支持多线程 ----》 同步和互斥 ---》加锁 ---》超级锁 ----》 解释器在同一时刻只能解释一个线程
大量python库为了省事依赖于这种机制 ---》 python多线程效率低
GIL 即为从python 解释器由于上锁带了的同一时刻只能解释一个线程的问题
解决方案 :
* 不使用线程 转而使用进程
* 不实用c作为解释器 java c#都可以做python解释器
IO 密集型 程序中进行了大量IO操作,只有少量的CPU操作
在内存中进行了数据的交换的操作都可以认为是IO操作
特点 : 速度较慢,使用cpu不高
cpu密集型(计算密集型):大量的程序都在进行运算操作
特点 : cup占有率高
效率测试:
Line cpu 1.224205732345581
Line IO 4.142379522323608
Thread cpu 0.7009162902832031
Thread IO 3.458016872406006
Process cpu 0.6419346332550049
Process IO 1.8482108116149902
* 多线程的工作效率和单线程几乎相近,而多进程要比前两者有明显的效率提升
设计模式
设计模式代表了一种最佳实践,是被开发人员长期开发总结,用来解决某一类问题的思路方法。这些方法保证了代码的效率也易于理
解。
单例模式 工厂模式 生产者模式。。。。
生产者消费者模式
高内聚 : 在同一模块内,实现单一功能,尽量不使功能混杂
低耦合 : 不同的模块之间尽量相互独立,减少模块间的影响
总结
1.进程和线程的区别
2.会创建使用线程 threading
3.掌握基本的线程间同步互斥编程方法
4.知道什么是GIL
5.了解设计模式的概念