Python网络编程(线程通信、GIL、服务器模型)
什么是进程、进程的概念?
进程的概念主要有两点:
- 第一,进程是一个实体。每一个进程都有它自己的地址空间,
- 一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。
- 文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
- 第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),
- 它才能成为一个活动的实体,我们称其为进程。
- [3]进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,
- 描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
什么是线程、进程的概念?
- 通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源,
- 在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,
- 而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,
- 故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。
- 当下推出的通用操作系统都引入了线程,以便进一步提高系统的并发性,并把它视为现代操作系统的一个重要指标。
线程通信
线程通信方法:
多个线程共用线程空间,所以进程的全局变量对进程内线程均可见
线程的通信方法就是使用去全局变量通信
注:
线程间使用全局变量进程通信时,全局变量为共享资源
往往需要同步互斥机制
线程同步互斥:
线程Event
创建对象:
e = threading.Event()
事件阻塞函数:
e.wait([timeout])
设置事件:
e.set()
清除事件:
e.clear()
线程锁 Lock:
创建锁对象
lock = threading.Lock()
上锁:
lock.acquire()
解锁:
lock.release()
Pyhthon线程GIL问题:
GIL (全局解释器锁)
Python --->支持线程操作--->出现IO同步互斥--->加锁--->超级锁,给解释器加锁
由于Python为后端语言 所有的后端语言都支持线程操作、IO同步互斥、由于线程间
的数据安全及稳定性所以要加锁,如果每个线程都加锁开发以及运行的效率是非常低的
所以就出现了超级锁 就是给Python解释执行器加锁 由于加锁的原因而导致了GIL的出现
什么是GIL?
- GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念
- 由于不同的执行环境(解释器)所导致的例如Jpython C#py都没有GIL
- Python的多线程在多核CPU上,只对于IO密集型计算产生正面效果;
- 而当有至少有一个CPU密集型线程存在,那么多线程效率会由于GIL而大幅下降。
- GIL其实是功能和性能之间权衡后的产物,它尤其存在的合理性,也有较难改变的客观因素
后果:
同一时刻一个解释器只解释一个线程
此时其他线程需要等待。大大降低了Python线程的执行效率
只能实现并发不能实现并行
Python GIL问题解决方案:
由于太多的库依赖于GIL的这种环境 并没有太好的解决方案
帮助:
1.修改c解释器
2.尽量使用多进程进行并行操作
3.Python线程尽量用在高延迟多阻塞的IO情形
3.不使用CPython 使用C#、JAVA 做的得解释器
效率测试:
分别测试 多进程、多线程、单进程
执行相同的IO操作和CPU操作的时间
- Line cpu: 8.15166711807251 单进程cpu密集操作
- Line IO: 6.841825246810913 单进程IO密集操作
- Thread cpu 8.414522647857666 多线程cpu密集操作
- Thread IO 6.023292541503906 多线程IO密集操作
- Process cpu 4.079084157943726 多进程cpu密集操作
- Process IO 3.2132551670074463 多进程IO密集操作
测试环境:VM 2核虚拟机
由此我们可以看出 多次线程不仅执行实现没有单进程快 反而更慢了 这就是GIL所导致的
相比来说多进程就快很多 内核越多效率越明显 由于全局锁的原因 多线程并没有实现
并行只是并发效果
进程和线程的区别和联系
1.两者都是多任务编程的方式 都能够使用计算机的多核
2.进程的创建和删除要比线程消耗更多的计算机资源
3.进程空间独立,数据安全性好,有专门的进程间的通信方法
4.线程使用进程的全局变量,更加简单,但需要同步互斥操作
5.一个进程可以包含多个线程,线程共享进程空间资源
6.进程线程都独立执行,有自己的特有属性
使用情况:
1.一个进程中并发任务比较多,比较简单,适合使用多线程
2.如果数据程序比较复杂,特别是可能多个任务通信比较多的时候
要考虑使用线程同步互斥的复杂性
3.多个任务存在明显差异,和功能分离的时候没有必要一定写入到一个进程中
4.使用Python要考虑到GIL的问题
服务器模型:
硬件服务器:主机 集群
厂商:IBM、HP、联想、浪潮
软件服务器:
编写的服务端程序 依托于硬件服务器运行,提供给用户一定的功能服务
服务器种类:
webserver:
网络后端应用程序,提供数据处理和逻辑处理
httpserver:
接受http请求,返回http响应
邮箱服务器:
处理邮件请求,进行邮件收发
文件服务器:
提供文件的上传下载存储
功能实现上分类:
网络连接、逻辑处理、数据运算、数据交互、协议实现、网络数据传输.....
模型结构分类:
C/S 架构:
客户端服务器模型
B/S 架构:
浏览器服务器模型
服务器目标:
处理速度更快,数据安全性更强,并发量更高
硬件提高:
更好的配置,更好的硬件搭配,更高的网络速度,更多的主机,网络安全投入
软件提高:
占有更少的计算机资源,更稳定的运行效率,更流畅的速度,更强大的算法,更合理的技术搭配
网络服务器基础:
循环服务器:
单进程程序,循环接受客户请求,处理请求,处理完毕后再接受下一次请求
特点:
每次只能处理一个客户端请求,如果客户端长期占有服务器则无法处理其他客户端
请求
优点:
实现简单,占用资源少
缺点:
无法同时处理多个客户端,体验差
使用情况:
任务短暂,可以快速完成,udp比tcp更适合循环
并发服务器:
能够同时处理多个客户端任务请求
IO并发:
IO多路复用、协程
优点:
可以实现IO并发操作,占用系统资源少
缺点:
不能够监控CPU密集的情况,并不能有长期阻塞
多进程/线程并发:
为每个客户端单独提供一个进程或线程,处理客户端请求
优点:
客户端可以长期占有服务器
缺点:
消耗计算机资源比较多
1. 进程线程的区别和关系
区别:
进程和线程之间都有自己的特有属性 如TID PID 指令集、线程栈、进程栈等。。。
进程创建删除消耗计算机资源比较大而进程就很小理论上是20倍的关系但实际和电脑配置等差异颇大
进程的数据安全性较高,空间独立,运行独立,有专门的通信方法
而线程也可以运行独立但是空间是同进程内所有线程共享资源 没有专门通信方法
通信比较简单 但是这种通信会导致临界资源的安全性、稳定性、等不确定因素发生
使用线程是要考虑临界资源的问题 可以用同步互斥的方法解决
相对于Python而言由于Cpython解释器的运行环境原因导致GIL的问题 多进程的效率
以及等方面要高于多线程很多 多进程可以实现并行 而多线程由于全局锁的原因导致
只能并发而不能并行
线程是系统非配资源的最小单位 而线程没有资源的分配它是系统内核最小的使用单位
关系:
一个进程可以包含多个线程
线程的说有空间都是由线程空间分配出来的 多线程和主进程(线程)使用的全局变量都是同一个
线程和进程的创建都是需消耗计算机资源并且线程和进程都是一个相互独立运行的过程
单进程也可以叫做单线程
2. 进程间通信方式都用过哪些,有什么特点
进程间常用的通信方法有管道、消息队列、共享内存、信号、信号量、套接字等
管道、队列、共享内存都是在内存中开辟一快空间来进行通信的
管道和队列的特点多进程都可见都能够进行存取操作效率都一般 创建管道对象的函数有一个参数
可以设置管道的通信方法 队列当然也有他自己的特点 虽然多进程都可以进行读写操作但是
写入的数据都是顺序进行的取出也是一样 任意读取 数据是先进先出的顺序 并且两者都可以
写入python几乎所有数据类型 而共享内存虽说效率较高但是只能同时写入或读取一个数据或数据结构
共享内存的数据写入的字符串必须是二进制格式数据结构内的数据类型必须相同 每次写入会覆盖之前的
内容并且共享内存的读取数据不会清除内存中的数据而管道和队列会每次读取过的数据都会清除
共享内存内的临界资源存入的是数据结构若要输出打印需要for或while循环遍历输出 直接print会报错
信号一种通知利用一个进程对另一个进程发送信号另一个进程捕获信号并处理该信号对应的相应操作
例如:SIGKILL 终止进程 SIGSTOP暂停进程 当然也可以给自己发送信号 SIGALRM是一个时钟信号代表制定时间后结束进程
信号量是一定的数量根据数量的多少来进行不同的进程操作在内存中他就是一个数字 多进程都可以对信号量做加减操作
套接字和其他都不同进程与进程间的通信是以套接字服务来进行网络通信或磁盘通信 网络通信例如TCP/Ip协议
磁盘通信是本地套接字的进程通信方式 既然说是磁盘那么就是在磁盘中纯在的文件作为通信的临界区
3. 同步和互斥是怎么回事,你都用哪些方法实现了同步互斥
互斥:
互斥是一种制约关系,当一个进程或者线程进入临界区操作资源时采用上锁的方式,阻止其他进程操作,直到解锁后才能让出资源
就是某一个资源只能允许一个进程或线程访问(访问者),来达到唯一性和非他性,但是无法限制访问的顺序所以访问是无须的
是有在多个访问者或者多任务编程的情况下才会出现的一种各访问者间抢占临界资源的现象
同步:
同步是一种合作关系,为完成某个任务,多进程或者多个线程之间形成的一种协调按照约定执行,相互告知,共同完成任务
大多数情况下是在互斥的基础上,通过其他机制实现访问的有序性、而且一般情况下同步已经实现了互斥
特别是在写入资源的情况下尤为明显少数情况是许多访问者可以访问资源 例如数据库的读锁和写锁 当然
数据库的锁是由引擎自动加或解的
实现方法:
可以利用控制信号量的方式来实现也可以用Event事件来实现 当然更可以以使用线程锁来实现
其实用信号的自定义处理机制的方式也可以实现不过比较麻烦
4. 什么是僵尸进程,怎么处理的
是由于父进程不知道子进程什么时候结束父进程没有wait子进程退出状态 丢失子进程的结束信息无法处理
从而导致子进程结束后变为僵尸进程系统所能使用的进程号是有限的,如果大量的产生僵死进程,
将因为没有可用的进程号而导致系统不能产生新的进程危害还是比较大的
可以创建二级子进程 就是创建一个子进程然后用子进程创建二级子进程 让二级子进程执行 子进程退出
可以达到让二级子进程变为孤儿进程 进程有退出只有3中方式
也可以让父进程只用来创建和回收子进程 把说有的事情交各个子进程做
如果父进程不想关心子进程的退出可以也可以发送signal(SIGHLD, SIG_IGN)忽略信号给内核
让内核进行处理一切后事 并且不会在来骚扰父进程
最后就是可以使用wait或waitpid阻塞函数等待子进程退出 然后进行处理其实这种效率是不高的
因为不知道子进程的退出时间 就是死等 曾经有一份真挚的爱情摆在我面前我没有珍惜,等到失去后才后悔莫及
如果上天能再给我一次机会 我会对那个女孩说 我爱你 如果非要在这个爱上加一个期限的话 我希望是 一万年......................
孤儿进程:父进程先退出 子进程被系统制定进程收养并且处理一切后事
僵尸进程:子进程先退出 父进程不知道子进程退出 没有处理退出状态
正常退出:父进程等待子进程先退出处理退出状态后然后父进程退出
5. python线程的效率怎么样?GIL是怎么处理的
上面好像说了...吧 键盘敲坏了 溜了溜了...