您的位置:首页 > 理论基础 > 计算机网络

python学习之网络编程进阶系列(总结一)

2018-08-05 21:13 295 查看

 

A.应用程序----操作系统---硬件


​​​​
QQ----告诉操作系统要启动----操作系统就去将硬盘上的QQ代码加载到内存---
      ----紧接着操作系统让CPU去执行这段代码----QQ启动了

启动的QQ程序就是一个进程---进程一定是正在执行的程序

多道技术:----一种并发的实现
1.在程序有IO阻塞时,cpu在多个程序之间切换着执行,切换时记录上一个程序的执行状态
2.在程序运行时间长时,cpu也会切换执行
前者会使内存空间复用,且cpu时间复用,效率明显提升
后者是为了实现一种并发的效果,效率并没有提高,反而降低了,
                                    因为执行时间 = 多个程序单独执行时间之和 + 切换时间

B. 操作系统的作用以及并发、并行的概念

操作系统的作用:
1.隐藏复杂的硬件接口,提供给应用程序良好的抽象接口
2.管理、调度进程,并且将多个进程对硬件的竞争使用合理分配(其实就是分配CPU的使用权限)
eg:如果任由抢占式任务去无序竞争,那么同时竞争打印机资源的多个程序就会错乱打印,导致打印失败

单核模式下只能实现并发----看起来是同时运行
多核模式下才是并行:------真正的同时运行

C.进程、子进程、守护进程、互斥锁、队列

一、主进程与子进程

什么是进程:一个正在运行的程序
一个进程的三种状态:
1.运行  正在被CPU执行    CPU属于硬件是由操作系统管理的
2.就绪  随时准备被CPU执行
3.阻塞  比如在运行期间要去读取硬盘数据经过I/O阻塞后,必须要切换为就绪状态,才能被CPU随时切换执行
开启子进程的作用是什么?
进程就是一段正在执行具有某项功能的的代码
所以开启子进程就是为了可以并发式地去处理某项功能从而完成指定的任务----进程之间是相护独立的

父进程创建子进程经历了什么?
1.父进程开启子进程时并不是真正地去调用CPU执行子进程的代码,
2.而是向操作系统发送一个创建子进程的信号,
3.然后操作系统才去拷贝父进程的数据作为子进程的初始化,
4.进而调度CPU去执行这个子进程-------CPU执行子进程的时间取决于操作系统的调度,

所以父进程根本就控制不了子进程的运行(父与子进程就是一种相护隔离运行的状态)
此时父进程就继续运行自己,既然无法控制就潇洒地继续执行自己
[code]# 开启进程的两种方式-----使用原生类去创建对象
from multiprocessing import Process
import os

def task():
print('我是进程:%s' % os.getpid())

if __name__ == '__main__':
p1 = Process(target=task)
p1.start()
p1.join()
print('主:%s' % os.getpid())

# 开启进程的两种方式-----自定义继承自原生类的子类创建对象  子类必须定义为run方法
from multiprocessing import Process
import os

class MyProcess(Process):
def __init__(self, name):
super().__init__()
self.name = name

def run(self):
print('我是子进程:%s' % self.name)

if __name__ == '__main__':
p1 = MyProcess('wbj')
p1.start()
print(p1.name)
print(p1.is_alive())
# 因为进程独立  所以为了看到子进程的执行过程  在这里等待子进程执行完毕
p1.join()
print(p1.is_alive())
print('主:%s' % os.getpid())
一个程序的运行一定是由另一个程序将其加载到内存中,再由操作系统去调度CPU去执行的
比如我在pycharm中运行py文件  py文件运行后的ppid父进程ID一定就是pycharm运行的pid

简单说一下僵尸进程与孤儿进程(了解)

僵尸进程就是在p1.join( )等待子进程执行完毕时,照理说子进程就会被释放,但是此时父进程却还能查看p1的进程ID和进程名等状态信息,--------------------子进程执行完毕后会保留其状态信息,释放的是内存数据信息

孤儿进程就是父进程死了,子进程还活着----当子进程执行完毕后,会被python内部的一个组建将其内存数据和状态全部释放掉

 

二、守护进程

[code]from multiprocessing import Process
import time
import random

random.seed(time.time())

def Foo():
print('打印第一个')
time.sleep(random.randint(1, 3))
print('打印第二个')

def Func():
print('复制第一个')
time.sleep(random.randint(1, 3))
print('复制第二个')

if __name__ == '__main__':
p1 = Process(target=Foo)
p2 = Process(target=Func)
p1.daemon = True
p1.start()
p2.start()
time.sleep(1)
print('我是主进程')  # 守护进程在主进程死时也死了

两点注意:

1.守护进程必须在进程开启前设置好 p1.daemon = True

2.守护进程是没有子进程的(天生的职责就是守护主子)

三、进程互斥锁

进程之间是相互独立的
1.加锁可以保证多个进程修改同一块数据时,只能有一个先进行,即串行修改
2.效率低下,需要自己使用锁去处理
这里的同一块数据指的是硬盘上的数据,因为进程间是相互独立的

四、互斥锁之队列

queue的作用:
1.使进程间共享一块内存数据
2.自动加锁

生产者和消费者模型:
1.需要程序中出现两种角色  一种生产数据  另一种处理数据
2.生产者---队列---消费者

利用queue解决生产者消费者模型
优化一下low版本就是使用joinablequeue-----等待队列执行结束后  生产者才是真正意义上的进程结束


D.线程、子线程、守护线程、互斥锁、队列
 

------------------------------------------------注意  其实没有子线程的说法  只是为了好掌握一些而已
进程与线程:
1.进程是由线程组成的  一个进程至少包括一个线程  因为线程是进程执行的基本运行单元
2.同一个进程间的线程是共享资源的
3.进程是需要申请资源的,所以线程不耗费额外的资源,而是占用本进程申请好的资源
                                              ----所以在进程中启动线程,基本上是秒启动的,因为CPU不耗费任何额外的资源

一、线程

启动线程的两种方法: 类比进程
1.直接使用thread类
2.自己创建一个继承thread类

一个进程开启时就创建了一个线程  从资源角度看是主进程  从运行角度看是主线程

注意:对于单进程程序  就默认只有伴随进程开启的那一个线程

二、守护线程----跟随主线程死亡
1.主线程执行完毕时,非守护线程已经执行完毕,那么守护线程就伴随主线程立马死
2.主线程执行完毕----等待非守护线程执行完毕后,守护线程才会跟着主线程死,即使守护线程还没执行完毕

总结:就是只要主线程在执行完毕时,守护线程立马死

三、线程互斥锁
1.创建线程时,多线程共享资源就会有竞争----就需要锁来进行资源的分配

2.不能在线程全局加上互斥锁,需要在局部加上就行了,全局加锁还不如直接串行执行

全局互斥锁的概念(了解)----CPython独有的 GIL

# 保护解释器的数据
1.简单点就是python解释器在运行py代码时只有一个线程可以操作python解释器中的功能代码
---------------------------GIL保护的python解释器中的数据  一般跟垃圾回收机制有关

# 保护py代码的数据
2.python文件代码被作为python解释器的参数进行传递并在python解释器中执行,但是 GIL不保护py文件数据
--------------------因为python解释器中的功能代码和py文件代码分属两个不同的独立内存
------------------------------------------------------------------------------------------------------------
GIL 获取的是python解释器的使用权限(也就是获取py代码的执行权限)------mutex 是获得py代码中某些数据的修改权限
由于CPython的 GIL的 存在, 使得单个进程在执行时,只有一个线程在运作
----导致单进程的多线程无法利用多核技术(只能有并发的效果,没有并行)

--------------------------------------------------------------------------------------------------------------------------------------------------------

CPU 是计算的:
1.对于计算型的,CPU越多性能越好
2.对于I/O阻塞的,再多CPU其实也没用  因为此时单个CPU也能完成任务

计算密集型;(多进程)
CPU 越多  性能越好   ----多进程可以利用多核技术并行执行

I/O密集型:(多线程)
CPU就算多  在I/O阻塞时也会挂起等待   还不如单核CPU切换执行-----多线程并发执行

四、线程互斥锁

死锁:小明拿到 A 锁,小红拿到 B锁 小明等待B锁 小红等待A锁 陷入死循环

RLock 递归锁: 可以被多次acquire 直到递归计数为0,递归锁才允许被别的线程抢夺并acquire到

信号量:就是同时开启多把锁,就相当于有多个房间等待入住

[code]
from threading import Thread, Semaphore, current_thread
import time

def func():
with sm:
print('%s get sm ' % current_thread().getName())
time.sleep(3)

if __name__ == '__main__':
sm = Semaphore(3)  # 表示可以同时运行3个线程  类似于线程池
for i in range(10):
t = Thread(target=func)
t.start()

event 交互通讯的一种方式 e.wait----e.set

[code]from threading import Thread, Event
import time

def conn():
n = 0

while not event.is_set():  # 检测event是否被设置了,一旦被设置就触发执行
if n == 3:
print('尝试多次')
return
print('正在尝试连接')
event.wait(0.5)  # 超时等待时间  不给参数表示一直等待下去
n += 1

def check():
print('正在检查连接')
time.sleep(5)
event.set()

if __name__ == '__main__':
event = Event()
for i in range(3):
t = Thread(target=conn)
t.start()
t = Thread(target=check)
t.start()

Timer 开启定时器直接实例化 取消定时器 obj.cancel()

[code]from threading import Timer

def fun():
print('打印')

t = Timer(2, fun)  # 等待2s后,执行函数fun()
t.start()

print('主线程')

 

E.进程线程池----同步异步---回调函数--I/O阻塞(协程等)

 

一、进程池和线程池
实例化一个池子
关闭池子入口并等待任务执行完毕

 

同步:任务在提交后,要在原地等待结果再去执行下一个任务------串行执行
异步:任务提交完毕后,不用在原地待命,继续执行下一个任务----并行执行

注意:同步不是阻塞,只是一种提交方式。可能是任务提交后,计算密集型函数运行需要花费不少的时间,但这并不是I/O阻塞耽误时间

回调机制:就是触发和响应----A触发B,B 执行响应 A   B函数的参数是A的实例化对象  想要得到A的返回值  就需要A.result()取值

什么函数能成为回调函数?  就是该函数的执行依赖别的函数的执行结果

 

线程也是运行、就绪、阻塞三种状态

协程:单线程下实现并发,又称是微线程
1. 协程是一种用户态的轻量级线程
2. 即协程就是由用户程序自己控制调度的
--------------------------------------协程目的是用来降低自己线程的I/O阻塞,提升自己的就绪态更多得到CPU的执行

协程使得程序执行效率更加的高(最大限度地利用 CPU)---因为I/O阻塞低

协程必须要做到:遇到I/O阻塞后自行检测,然后用户控制在此期间的任务调度,实现任务间的来回切换,从而降低阻塞

greenlet  模块不会实现检测I/O阻塞去自动切换任务 g = greenlet(func)  g.switch()----手动切换
gevent  可以实现遇到I/O阻塞进行任务切换

服务端收取消息:1.等待自己系统缓存中有消息  2.拷贝消息到自己的内存中

非阻塞I/O: server.setblocking(False)
1.不能及时响应数据
在询问状态时得到False后,会执行别的的事,那么在执行别的事情的时候会出现消息已经为True
2.一直这样循环非阻塞I/O,CPU占用率会大幅度升高
一直处于计算密集型,陷入一种死循环状态

多路复用 I/O模型: select可以查看多个socket连接的状态
----------------------如果只有一个连接那select多路复用反而更加耗时,因为select充当中介也占用时间

1.select代理 监测服务端所有套接字对象的状态  通过去询问操作系统
2.根据状态判断去执行任务

优点是:在监测的套接字少的情况下,效率大大提高了
缺点是:监测数量大时,效率就慢了
--------------因为select代理询问状态,操作系统需要循环给的数据是否为True,全部统计完毕才返回

---------------------------------------------------------------------补充总结

队列queue在进程中的作用:
1.实现进程间的数据共享
2.实现对数据的加锁行为

队列queue在线程中的作用:
1.实现对数据的加锁行为

 

 

阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: