疫情环境下的网络学习笔记 python 4.24
4.24作业
整理并发编程三天内容理论,用自己的概述
-
多道技术
允许多个程序进入内存,由CPU交替执行。当一个程序进入IO操作,这个程序进入阻塞,所以此时CPU马上切换到另一个程序运行,这样在等待IO操作的时候CPU也能得到利用,榨干CPU
-
进程理论
所有程序进入运行态前必须经过就绪态排队

- 运行态就是程序运行中的计算,创建名称空间,print,打开文件一类的
- 阻塞态就是CPU不做运算的状态,包括time.sleep,input一类
- 就绪态是阻塞态结束,包括time.sleep结束,input输入之后获取到值一类。结束后进入就绪态排队,等待CPU来运行
-
同步和异步
描述任务的提交方式
-
同步:原地等待结果,得到结果后继续往下运行
-
异步:不等待结果,直接做其他事情,微信小程序里的onLoad,通过回调机制获取结果
程序的最佳形式是异步+非阻塞
-
开启进程的两种方式
-
进程要运行的代码写在函数内,使用 multiprocessing 模块,生成对象,指定target,args,调用对象的start方法,开启进程
from multiprocessing import Process def task(name): pass if __name__ == '__main__': p = Process(target=task,args=('deimos',)) p.start()windows操作系统下,开启进程一定要在main里开启:因为Windows中创建子进程会自动import当前这个py文件,因此会发生递归,所以要用main保护起来,import的时候不调用
-
继承Process类的方法
导入multiprocessing,写一个类,类里面定义run函数,run里面放进程要做的事情,生成对象,调用start方法,会启动进程,执行run
from multiprocessing import Process import time class MyProcess(Process): def run(self): print('sleep') time.sleep(1) print('get up') if __name__ == '__main__': p = Myprocess() p.start() print('main')
创建进程就是在内存中申请一块内存空间,将需要运行的代码丢进去,一个进程对应在内存中就是一块独立的内存空间
-
-
join方法
主进程等待子进程结束之后,再继续往后执行,结果就是把异步变成同步
-
进程对象其他方法
一台计算机上运行着很多进程,计算机会给每一个运行的进程分配一个 PID号 ,是每一个进程的唯一标识,可以通过multiprocessing的current_process和os模块查看
from multiprocessing import Process,current_process import os print('%s is running'%current_process().pid) # 获取当前pid print(os.getpid()) # 获取父进程的pid print(os.getppid()) # 查看一个是否存活 p.is_alive() # 杀死一个进程 p.terminate() -
僵尸进程与孤儿进程
- 僵尸进程:父进程开设了子进程,在父进程结束之后不会立马释放PID号,而是会保留一段时间,可以通过父进程查看子进程
- 孤儿进程:子进程在运行,父进程结束了,则操作系统会接管,回收子进程的资源
-
守护进程
在start开启进程上方,指定进程p为守护进程
p.daemon = True- 其一:守护进程会在主进程代码执行结束后立即终止
- 其二:守护进程内无法再开启子进程,否则抛出异常
指定守护进程必须在start上方
-
互斥锁
多个进程操作同一份数据的时候,会出现数据错乱的问题,针对上述问题,加锁处理:将并发变成串行,牺牲效率,保证了数据安全
原理:在主进程中生成一把锁,让所有的子进程抢,谁先抢到,谁先操作数据。一个进程抢到了,进程结束释放之后,别的进程接着抢
from multiprocessing import Process,Lock # 从这个模块里导入Lock # 在每个进程抢文件的操作前生成一个锁 mutex.acquire() # 抢到了锁,对文件操作 buy(i) # 操作完了,释放锁给别的进程抢 mutex.release()实际编程中很少使用锁,使用锁的时候有可能造成死锁现象
-
队列 Queue
先进先出,创建队列对象,有一系列方法可以使用
使用队列
- 使用 multiprocessing,从中导入Queue:
from multiprocessing import Queue,直接使用q = Queue()
from multiprocessing import Queue # 创建一个队列,括号内可以传数字,表示生成队列最大可以存放的数据量max_size q = Queue(5) # 往队列中存数据:put q.put(100) q.put(200) q.put(300) # 当队列数据放满了,继续put,程序会阻塞,直到有位置让出来,多的数据入队 # 在队列中取数据:get val = q.get() # 100 # 队列中如果已经没有数据,get方法会原地阻塞 val6 = q.get_nowait() # 没有数据直接报错 val7 = q.get(timeout = 3) # 等待时间3秒,超过3秒没有取出数据,报错 q.full() # 返回判断队列是否满了 q.empty() # 返回判断当前判断是否为空 # 当多进程操作一个队列的时候,结果会不准确,所以使用队列的时候配合锁一起操作数据 - 使用 multiprocessing,从中导入Queue:
-
借助队列实现进程间通信
上个例子
from multiprocessing import Process,Queue # 1. 主进程跟子进程借助队列通信 def producer(q): q.put('1') print('producer') # 2. 子进程跟子进程借助队列通信 def consumer(q): print(q.get()) if __name__ == '__main__': q = Queue() p = Process(target=producer, args=(q,)) p1 = Process(target=consumer, args=(q,)) p.start() p1.start() -
生产者消费者模型
就是借助队列,一个进程产生数据,一个进程接收数据,两个角色之间需要一个媒介来传输数据:队列
get和put都会原地阻塞,所以会一直产生一直接收
要消费者结束接收,可以让生产者在结束的时候生产指定的符号,让消费者接收
# 生产者+消息队列+消费者 # JoinableQueue:可以被等待的,带有计数器的queue # 在往队列中放数据的时候,计数器自动+1,从队列中取数据的时候,调用task_done方法,计数器自动-1 q.join() # 当计数器为0的时候才继续往下运行 -
线程理论
- 进程表示的是资源单位,真正干活的是线程,执行单位
- 将操作系统比喻成一个工程,进程相当于车间,线程相当于车间里的流水线
- 进程和线程都是虚拟单位,只为了更加方便地描述问题
-
开启线程的方式
-
与开启进程相似,使用模块生成对象,指定target;继承类
-
同一进程下的线程数据共享,线程也可以像进程一样使用join方法
-
线程也可以指定守护线程,和守护进程方法一样
t = Thread(target=task) t.daemon = True t.start() -
线程也可以使用互斥锁,与进程一样
from multiprocessing import Lock mutex = Lock() # 在每个线程中 mutex.acquire() # 操作 mutex.release()
from threading import Thread import time def task(name) print('%s running'%name) time.sleep(1) print('%s over'%s) # 开启线程不需要在main下执行代码,直接书写就可以 # 但是还是习惯性地将启动命令写在main下面 t = Thread(target=task,args=('aaa',)) t.start() # 创建线程的开销非常小,几乎代码一执行,就创建了# 第二种方式 from threading import Thread import time class MyThread(Thread): def __init__(self,name): super().__init__() self.name = name def run(self): print('%s running'%name) time.sleep(1) print('%s over'%s) if __name__ == '__main__': t = MyThread('aaa') t.start() print('主线程') -
-
使用多线程实现TCP并发
# 服务端 server = socket.socket() # 不写参数,默认是TCP server.bind(('127.0.0.1',8080)) server.listen(5) # 将服务的代码单独封装成一个函数 def talk(conn): while True: try: data = conn.recv(1024) if len(data == 0):break print(data.decode('utf-8')) except Exception as e: print(e) conn.close() # 连接循环,使用线程实现并发 while True: conn,addr = server.accept() # 自动阻塞 # 一旦接收到链接,就开启一个线程 t = Thread(target=talk,args=(conn,)) t.start() -
互斥锁可以使用with上下文
with mutex: tmp = money time.sleep(0.1) money = tmp - 1 # 自动抢锁,结束了自动释放锁 -
全局解释器锁GIL
解释器的特性,不是python的特性
GIL保证同一时刻只有一个线程执行代码,每个线程在执行过程中都要先获取GIL
因为GIL的存在,python中的多线程其实是伪多线程,即使在多核的情况下也只能发挥出单核的性能
掌握如何开设进程和如何开设线程的代码
开设进程
# 1. 导入multiprocessing,Process
# 2. 把进程要做的事情放在函数里
# 3. 生成Process对象,指定target为进程的函数,args为函数参数
# 4. 可选:p.daemon=True可以指定守护进程
# 5. start() 开启进程
开设线程
# 1. 导入threading,Thread
# 2. 把进程要做的事情放在函数里
# 3. 生成Thread对象,指定target为线程的函数,args为函数参数
# 4. 可选:p.daemon=True可以指定守护线程
# 5. start() 开启线程
利用多进程或多线程自己实现TCP服务端的并发
# 服务端
import socket
from threading import Thread
# 服务端
server = socket.socket() # 不写参数,默认是TCP
server.bind(('127.0.0.1', 8080))
server.listen(5)
# 将服务的代码单独封装成一个函数
def talk(conn):
while True:
try:
data = conn.recv(1024)
data = data.decode('utf-8')
print('接收到的信息:',data)
conn.send('歪比巴布'.encode('utf-8'))
if len(data) == 0 : break
except Exception as e:
print(e)
conn.close()
# 一个链接退出之后,当前线程结束,主进程中仍然在接收链接
break
# 连接循环,使用线程实现并发
while True:
conn, addr = server.accept() # 自动阻塞
print(conn,addr)
# 一旦接收到链接,就开启一个线程
t = Thread(target=talk, args=(conn,))
t.start()
# 客户端
import socket,time
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
client.send('client 1'.encode('utf-8'))
data = client.recv(1024)
print(data.decode('utf-8'))
time.sleep(0.5)
整理python基础阶段知识点及项目代码,ATM购物车,选课系统一定要自己脱稿从头到位敲出来
预习并发编程剩余知识点
GIL锁与自定义锁,多线程工作顺序图解
https://www.cnblogs.com/the3times/


浙公网安备 33010602011771号