并发编程
内容概要
-
并发编程理论与操作系统发展史
-
多道技术
-
近程理论及调度算法
内容概要 -
同步与异步
-
阻塞与非阻塞
-
创建进程的两种方式
-
进程join方法
-
进程间数据隔离
-
进程间通信之IPC机制
-
进程对象诸多方法
-
生产者消费者模型
-
互斥锁简介
-
多进程实现TCP服务端并发
-
互斥锁代码实操
-
线程理论
-
创建线程的两种方式
-
线程的诸多特性
-
GIL全局解释器锁
-
验证GIL的存在
-
GIL与普通互斥锁
-
死锁现象
-
信号量
-
event事件
-
进程池与线程池
-
协程
-
协程实现并发
并发编程理论
'''在计算机中主要是cpu进行工作'''
操作系统发展史
1.穿孔卡片阶段
计算机很庞大 使用很麻烦 一次只能给一个人使用 期间很多时候计算机都不工作
好处:程序员独占计算机 为所欲为
坏处:计算机利用率太低 浪费资源
2.联机批处理系统
提前使用磁带一次性录入多个程序员编写的程序 然后交给计算机执行
CPU工作效率有所提升 不用反复等待程序录入
3.脱机批处理系统
极大地提升了CPU的利用率
总结:CPU提升利用率的过程
多道技术
'''在学习并发编程的过程中 不做可以提醒的情况下,默认一台计算机就只有一个cpu'''
# 单道技术
所有的程序排队执行 过程中不能重合
# 多道技术
利用空闲时间提前准备其他数据 最大化提升CPU利用率
# 多道技术详细
1.切换
计算机的CPU在两种情况下会切换(不让你用 给别人用)
1.程序有IO操作
输入\输出操作
input、time.sleep、read、write
2.程序长时间占用CPU
我们得雨露均沾 让多个程序都能被CPU运行一下
2.保存状态
CPU每次切换走之前都需要保存当前操作的状态 下次切换回来基于上次的进度继续执行
近程理论
进程与程序的区别
程序:一堆死代码(还没有被运行起来)
进程:正在运行的程序(被运行起来了)
进程的调度算法(重要)
1.FCFS(先来先服务)
对短作业不友好
2.短作业优先调度
对长作业不友好
3.时间片轮转法+多级反馈队列(目前还在用)
将时间均分 然后根据进程时间长短再分多个等级
等级越靠下表示耗时越长 每次分到的时间越多 但是优先级越低
进程的并行与并发
并行
多个进程同时执行 必须要有多个CPU参与 单个CPU无法实现并行
并发
多个进程看上去像同时执行 单个CPU可以实现 多个CPU肯定也可以
进程的三状态
就绪态
所有的进程在被CPU执行之前都必须先进入就绪态等待
运行态
CPU正在执行
阻塞态
进程运行过程中出现了IO操作 阻塞态无法直接进入运行态 需要先进入就绪态
同步与异步
同步与异步是用来表达任务的提交方式
1.同步
提交完任务后原地等待任务的返回结果,期间不做任何事情
2.异步
提交完任务之后不原地等待任务的返回结果,直接去做其他事情,有结果自动通知
阻塞与非阻塞是用来表示任务的执行状态的
阻塞
阻塞态
非阻塞
就绪态、运行态
# 阻塞与非阻塞 一般与 同步异步 一起综合使用
同步阻塞>>>>>>>>>效率最低
同步非阻塞
异步阻塞
异步非阻塞>>>>>>>效率最高
创建进程的多种方式
"""
1.在桌面双击软件图标,创建进程
2.使用python代码创建进程
在不同的操作系统中创建进程底层原理不一样
windows
以导入模块的形式创建进程
linux/mac
以拷贝代码的形式创建进程
"""
from multiprocessing import Process
def task(num):
print(f'{num}开始表演~')
# 由于windows系统在创建进程的时候会单独开辟一个空间去运行进程代码
# 以导入模块的形式创建进程
# 这样的话这个空间会运行到创建进程的这行代码
# 就相当于一直在重复的创建进程,所以会报错
# 我们就可以通过__name__来限制他,如果他不是执行文件的话就不会运行 if __name__ == '__main__': 下面的代码
if __name__ == '__main__':
# Process 创建进程,通过 target指定子进程执行的任务是task函数
# 由于task需要传参,在创建子进程的时候通过args传参数给子进程中的task函数
# 这样就实现了进程的创建
p = Process(target=task, args=('牛牛',)) # 创建进程对象
p.start() # 子进程执行(异步操作)
进程对象的多种方法
1.如何查看进程号
from multiprocessing import Process, current_process
current_process()
current_process().pid
import os
os.getpid()
os.getppid()
2.终止进程
p1.terminate()
ps:计算机操作系统都有对应的命令可以直接杀死进程
3.判断进程是否存活
p1.is_alive()
进程间的join方法
from multiprocessing import Process
import time
def thing(name, age, num):
print(f'''
姓名:{name} 年龄:{age} 正在子进程中跑
''')
time.sleep(num)
print(f'{name}跑完了')
if __name__ == '__main__':
p1 = Process(target=thing, args=('小明', 18, 1))
p2 = Process(target=thing, args=('小李', 19, 2))
p3 = Process(target=thing, args=('小白', 20, 3))
start_time = time.time()
# p.start() # 异步
'''主进程代码等待子进程代码运行结束再执行'''
# p.join()
p1.start()
p2.start()
p3.start()
p1.join()
p2.join()
p3.join()
end_time = time.time()
print(f'''主程序运行时间为: {end_time - start_time} 秒''')
# 3s 多一些, 这是因为在p1.join的时候别的程序也在进行,所以是3s,但是如果将各自的join方法放在各自.start()方法下面的话就会变成6s多
进程间数据隔离
# 同一台计算机上的多个进程数据是严格意义上的物理隔离(默认情况下)
from multiprocessing import Process
num = 1000
def task():
global num
# 虽然这边是global可以修改全局变量,
# 但是这是修改子进程中的全局变量,与主进程无关
num = 666
print(f'子进程中的Num为:{num}')
if __name__ == '__main__':
p = Process(target=task)
p.start() # 异步运行task 命令~
print(f'主进程中的num为:{num}')
# 打印结果为1000 这也证明了
# 子进程中的数据修改和主进程中的数据是无关的
IPC机制
IPC=进程间通信
1.消息队列:
消息队列:存储数据的地方 所有人都可以存,也可以取
from multiprocessing import Queue
q = Queue() # 括号内能指定存储数据的个数,默认为:2147483647
队列:先进先出 堆栈:先进后出
我使用Put 放置了三个数据,取了四次,程序就会等什么时候又出现了数据,等待取出
反之放置了4个数据,直接运行,队列就会等第一个出去的数据,而并不会报错
消息队列方法
from multiprocessing import Queue
q = Queue() # 产生消息队列对象,括号内能指定存储数据的个数,默认为:2147483647
q.put() # 存放数据
q.get() # 取数据
q.empty() # 判断队列是否为空
q.full() # 判断队列是否已满
"""
full() empty() 在多进程中都不能使用!!!
"""
生产者消费者模型
生产者
负责产生数据的'人'
消费者
负责处理数据的'人'
该模型除了又生产者和消费者之外还必须有消息队列
(只要是能够提供数据和保存服务和提取服务的理论上都可以)
守护进程
守护进程会随着守护的进程结束而立刻结束
import time
from multiprocessing import Process
def run(name):
print(f'你好{name}!!')
time.sleep(3)
print(f'你好{name}!!')
if __name__ == '__main__':
p1 = Process(target=run, args=('小明',))
p1.daemon = True # 修改daemon的状态为True 就代表着 p1进程为主进程的守护进程,如果主进程运行完毕,子进程立即结束运行
p1.start()
time.sleep(1)
print('我要结束了!!')
僵尸进程与孤儿进程
僵尸进程
进程执行完毕之后并不会立刻销毁所有的数据,可能会有一些信息短暂的保留下来
# 进程号、进程执行时间、进程所消耗功率等等...
# 所有的进程都会变成僵尸进程
孤儿进程
子进程正常运行 父进程意外死亡 操作系统针对孤儿进程会派遣一个程序去管理他
多进程数据错乱问题
模拟抢票软件
from multiprocessing import Process
import time
import json
import random
# 查票
def search(name):
with open(r'data.json','r',encoding='utf8') as f:
data = json.load(f)
print(f'{name}在查票,当前余票为:{data.get('ticket_num')}')
# 买票
def buy(name):
# 再次确认票
with open(r'data.json', 'r', encoding='utf8') as f:
data = json.load(f)
# 模拟网络延迟
time.sleep(random.randint(1, 3))
# 判断是否有票 有就买
if data.get('ticket_num') > 0:
data['ticket_num'] -= 1
with open(r'data.json', 'w', encoding='utf8') as f:
json.dump(data, f)
print('%s买票成功' % name)
else:
print('%s很倒霉 没有抢到票' % name)
def run(name):
search(name)
buy(name)
if __name__ == '__main__':
for i in range(10):
p = Process(target=run, args=('用户%s'%i, ))
p.start()
"""
多进程操作数据很可能会造成数据错乱>>>:互斥锁
互斥锁
将并发变成串行 牺牲了效率但是保障了数据的安全
"""
## 多进程实现TCP服务端并发
```python
# 服务端
import socket
from multiprocessing import Process
def get_server():
server = socket.socket()
server.bind(('127.0.0.1', 9029))
server.listen(5)
return server
def get_talk(sock):
while True:
info = sock.recv(1024)
print(info.decode('utf8'
))
sock.send(info.upper())
if __name__ == '__main__':
server = get_server()
while True:
sock, addr = server.accept()
# 开设多进程去服务客户端
p1 = Process(target=get_talk, args=(sock,))
p1.start()
# 客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1',9029))
while True:
client.send(b'hello world')
info = client.recv(1024)
print(info.decode('utf8'))
互斥锁代码实操
锁:建议只添加在操作数据的部分,否则整个程序的效率会极低
import json
from multiprocessing import Process,Lock
import time
import random
def search(name):
with open(r'data.json','r',encoding='utf8') as f:
data = json.load(f)
print(f'''
{name} 正在查看票 目前剩余:{data.get('ticket')} 张
''')
def buy(name):
# 先查询票数
with open(r'data.json','r',encoding='utf8') as f:
data = json.load(f)
time.sleep(random.randint(1,3))
if data.get('ticket') > 0:
with open(f'data.json','w',encoding='utf8') as f:
data['ticket'] -= 1
json.dump(data,f)
print(f'{name} 买票成功~')
else:
print(f'{name} 买票失败。非常可怜~')
def run(name,mutex):
search(name)
mutex.acquire() # 锁
buy(name)
mutex.release() # 释放锁
if __name__ == '__main__':
mutex = Lock()
for i in range(10): # 产生10个进程
p1 = Process(target=run,args=('用户%s号' % i,mutex))
p1.start()
# 我们通过加上锁的操作就可以让数据不混乱,由于到buy函数之前就开始进行抢锁操作,所以在这个时候会变成同步,虽然牺牲了效率但是提升了数据的安全性
"""
锁有很多种 但是作用都一样
行锁 表锁 ...
"""
线程理论
线程
进程其实是资源单位 表示一块内存空间
线程
线程才是执行单位 表示真正的代码指令
我们可以将进程比喻为车间,那么线程就是车间当中的流水线
一个进程内部至少含有一个线程
1.一个进程内可以开设多个线程
2.同一个进程下的多个线程数据是共享的
3.创建进程和线程的区别
创建进程的消耗要远远大于线程
创建线程的两种方式
from threading import Thread
from multiprocessing import Process
import time
def task(name):
print(f'{name} is running')
time.sleep(0.1)
print(f"{name} is over")
if __name__ == '__main__':
start_time = time.time()
# p_list = []
# for i in range(50):
# p = Process(target=task,args=(f'用户:{i} 号',))
# p.start()
# p_list.append(p)
# for p in p_list:
# p.join()
# print(time.time() - start_time)
t_list = []
for i in range(1000):
t = Thread(target=task, args=('用户%s'%i,))
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(time.time() - start_time)
"""
创建线程无需考虑反复执行的问题
"""
class MyThread(Thread):
def run(self):
print('run is running')
time.sleep(1)
print('run is over')
obj = MyThread()
obj.start()
print('主线程')
线程的诸多特性
线程的特性与进程的特性差异不多
1.join 方法
2.同进程内多个线程数据共享
3.current_thread()
4.active_count() # 现存在的线程
GIL全局解释器锁
# 官方文档对GIL的解释
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.
'''
1.在Cpython解释器中存在全局解释器锁简称GIL
在Python解释器中有很多类型
Cpython JPython PYPython (最常用的就是Cpython解释器)
2.GIL本质上也是一把互斥锁
用来阻止同一个进程内多个线程同时执行
3.GIL的存在就是因为CPython解释器中内存管理,线程不是安全的(垃圾回收机制)
垃圾回收机制(引用计数、分代回收、标记清除)
'''
GIL与普通互斥锁
'''在CPython解释器中虽然有GIL全局解释器锁,但是它只能保证进程内多线程的数据不会被垃圾回收机制 弄乱
但是并不能确保程序里面的数据是否安全
'''
import time
from threading import Thread,Lock
num = 100
def task(mutex):
global num
# mutex.acquire()
count = num
time.sleep(0.1)
num = count - 1
# mutex.release()
mutex = Lock()
t_list = []
for i in range(100):
t = Thread(target=task,args=(mutex,))
t.start()
t_list.append(t)
# for t in t_list:
# t.join()
print(num)
python中多线程是否有用
需要分情况
情况1
单个CPU
多个CPU
情况2
IO密集型(代码有IO操作)
计算密集型(代码没有IO)
1.单个CPU
IO密集型
多进程
申请额外的空间 消耗更多的资源
多线程
通过多道计数 资源消耗相对较少
# 多线程更具有优势
计算密集型
多进程
申请额外的空间 消耗更多的资源(总耗时+申请空间+拷贝代码+切换)
多线程
消耗资源相对较少 通过多道技术(总耗时+切换)
# 多线程有优势!!!
2.多个CPU
IO密集型
多进程
总耗时(单个进程的耗时+IO+申请空间+拷贝代码)
多线程
总耗时(单个进程的耗时+IO)
# 多线程有优势!!!
计算密集型
多进程
总耗时(单个进程的耗时)
多线程
总耗时(多个进程的综合)
# 多进程完胜!!!
from threading import Thread
from multiprocessing import Process
import os
import time
def work():
# 计算密集型
res = 1
for i in range(1, 100000):
res *= i
if __name__ == '__main__':
# print(os.cpu_count()) # 12 查看当前计算机CPU个数
start_time = time.time()
p_list = []
for i in range(12): # 一次性创建12个进程
p = Process(target=work)
p.start()
p_list.append(p)
for p in p_list: # 确保所有的进程全部运行完毕
p.join()
# t_list = []
# for i in range(12):
# t = Thread(target=work)
# t.start()
# t_list.append(t)
# for t in t_list:
# t.join()
print('总耗时:%s' % (time.time() - start_time)) # 获取总的耗时
"""
计算密集型
多进程:5.665567398071289
多线程:30.233906745910645
"""
def work():
time.sleep(2) # 模拟纯IO操作
if __name__ == '__main__':
start_time = time.time()
# t_list = []
# for i in range(100):
# t = Thread(target=work)
# t.start()
# for t in t_list:
# t.join()
p_list = []
for i in range(100):
p = Process(target=work)
p.start()
for p in p_list:
p.join()
print('总耗时:%s' % (time.time() - start_time))
"""
IO密集型
多线程:0.0149583816528320
多进程:0.6402878761291504
"""
死锁现象
acquire()
release()
from threading import Thread,Lock
import time
mutexA = Lock()
mutexB = Lock()
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print(f'{self.name}抢到了A锁')
mutexB.acquire()
print(f'{self.name}抢到了B锁')
mutexB.release()
print(f'{self.name}释放了B锁')
mutexA.release()
print(f'{self.name}释放了A锁')
def func2(self):
mutexB.acquire()
print(f'{self.name}抢到了B锁')
time.sleep(1)
mutexA.acquire()
print(f'{self.name}抢到了A锁')
mutexA.release()
print(f'{self.name}释放了A锁')
mutexB.release()
print(f'{self.name}释放了B锁')
for i in range(10):
obj = MyThread()
obj.start()
信号量
在python并发编程中信号量相当于多把互斥锁
from threading import Thread,Lock,Semaphore
import time
import random
sp = Semaphore(5) # 一次性产生5把锁
class MyThread(Thread):
def run(self):
sp.acquire()
print(self.name)
time.sleep(random.randint(1, 3))
sp.release()
for i in range(20):
t = MyThread()
t.start()
event事件
from threading import Thread,Event
event = Event() # 类似于创造了一个红绿灯
def light():
print('红灯正在亮,所有人不能动~')
time.sleep(3)
print('绿灯亮了,油门踩到底,给我冲~')
event.set()
def car(name):
print(f'{name} 正在等红灯')
event.wait()
print(f'{name} 加油门 飙车了~')
t = Thread(target=light)
t.start()
for i in range(20):
t = Thread(target=car,args(f'小明PLUS {i} 号'))、
t.strat()
进程池与线程池
进程与线程不能无限制的创建,
由于硬件的发展跟不上软件的发展,有物理极限,如果我们在编写代码的过程中无限制的创建进程或者线程就可能会导致计算机崩溃
池
降低程序的执行效率 但是保证了计算机硬件的安全
进程池
提前创建好固定数量的进程共后续程序的调用
线程池
提前创建好固定数量的线程供后续程序的调用 超出则等待
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import os
import time
import random
from threadding import current_thread
# 1.产生含有固定数量线程的线程池
pool = ProcessPoolExecutor(5)
def task(n):
print('task is running')
time.sleep(random.randint(1,3))
print('task is over ',n,current_thread().name)
print('task is over',os.getpid())
return '我是task函数的返回值'
def func(*args,**kwargs):
print('from func')
if __name__ == '__main__':
# 将任务提交给线程池即可
for i in range(20):
# res = pool.submit(task,123) # 向线程池提交任务
# print(res.result()) 不能直接获取
# pool.submit(task,123).add_done_callback(func)
携程
'''
进程:资源单位
线程:执行单位
协程:单线程实现并发(效率极高)
在代码层面欺骗CPU 让CPU觉得我们的代码里面没有IO操作
实际上IO操作被我们自己编写的代码检测 一旦有 立刻让代码执行别的(该技术完全是有程序员自己做出来的,名字也是程序员自己起的)
核心:自己写代码完成切换+保存状态
'''
import time
from gevent import monkey;
monkey.patch_all() # 固定编写 用于检测所有的IO操作(猴子补丁)
from gevent import spawn
def func1():
print('func1 running')
time.sleep(3)
print('func1 over')
def func2():
print('func2 running')
time.sleep(5)
print('func2 over')
if __name__ == '__main__':
start_time = time.time()
# func1()
# func2()
s1 = spawn(func1) # 检测代码 一旦有IO自动切换(执行没有io的操作 变向的等待io结束)
s2 = spawn(func2)
s1.join()
s2.join()
print(time.time() - start_time) # 8.01237154006958 协程 5.015487432479858
协程实现并发
import socket
from gevent import monkey;monkey.patch_all() # 固定编写 用于检测所有的IO操作(猴子补丁)
from gevent import spawn
def communication(sock):
while True:
data = sock.recv(1024)
print(data.decode('utf8'))
sock.send(data.upper())
def get_server():
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
sock, addr = server.accept() # IO操作
spawn(communication, sock)
s1 = spawn(get_server)
s1.join()
如何不断的提升程序的运行效率
多进程下开多线程 多线程下开协程

浙公网安备 33010602011771号