GIL与普通互斥锁区别、验证多线程是否有用、死锁现象(强调锁不能轻易使用)、信号量与event事件(了解)、进程池与线程池(重点)、协程
GIL与普通互斥锁区别
互斥锁是运用在一个py文件中的,也就是在一个应用程序中,是代码层面的锁。
其实,Python解释器也是一个应用程序。只是说这个应用程序不是我们实现的,我们自己的python程序都要运行在解释器之上,这个应用程序被用来帮我们运行我们自己的程序。
线程互斥锁是Python代码层面的锁,解决我们自己写的Python程序中多线程共享资源的问题。
GIL是Python解释器层面的锁,解决解释器中多个线程的竞争资源问题。
"""
GIL是一个纯理论知识 在实际工作中根本无需考虑它的存在
GIL作用面很窄 仅限于解释器级别
后期我们要想保证数据的安全应该自定义互斥锁(使用别人封装好的工具)
"""
# 1.先验证GIL的存在
from threading import Thread,Lock
import time
money = 100
def task():
global money
money -= 1
for i in range(100):
t = Thread(target=task)
t.start()
print(money)
# 2.再验证不同数据加不同锁
from threading import Thread,Lock
import time
money = 100
mutex = Lock()
def task():
global money
mutex.acquire()
tmp = money
time.sleep(0.1)
money = tmp - 1
mutex.release()
t_list = []
for i in range(100): # 创建一百个线程
t = Thread(target=task)
t.start()
t_list.append(t)
for t in t_list:
t.join()
# 为了确保结构正确 应该等待所有的线程运行完毕再打印money
print(money)
验证多线程作用
# 验证多线程作用需要两个前提:
cpu个数:单个、多个
任务的类型:IO密集型、计算密集型
# 单个CPU
'''多个IO密集型任务'''
多进程:浪费资源 无法利用多个CPU
多线程:节省资源 切换+保存状态
'''多个计算密集型任务'''
多进程:耗时更长 创建进程的消耗+切换消耗
多线程:耗时较短 切换消耗
# 多个CPU
'''多个IO密集型任务'''
多进程:浪费资源 多个CPU无用武之地
多线程:节省资源 切换+保存状态
'''多个计算密集型任务'''
多进程:利用多核 速度更快
多线程:速度较慢
结论:多进程和多线程都有具体的应用场景 尤其是多线程并不是没有用!!!
计算密集型任务时
# 阿巴阿巴阿巴
from multiprocessing import Process
from threading import Thread
import time
import os
def wotk():
res = 1
for i in range(1, 100000):
res *= i
if __name__ == '__main__':
print(os.cpu_count())
start_time = time.time()
p_list = []
for i in range(4):
p = Process(target=wotk)
p.start()
p_list.append(p)
for p in p_list:
p.join()
# t_list = []
# for i in range(4):
# t = Thread(target=wotk)
# t.start()
# t_list.append(t)
# for t in t_list:
# t.join()
print('总耗时:%s' % (time.time() - start_time))
# 多进程总耗时: 5.134309530258179
# 多线程总耗时: 17.695664644241333
结论:
在进行计算密集型任务是多进程更好
多个IO密集型任务
def work():
time.sleep(3)
if __name__ == '__main__':
start_time = time.time()
# t_list = []
# for i in range(4):
# t = Thread(target=work)
# t.start()
# t_list.append(t)
# for t in t_list:
# t.join()
p_list = []
for i in range(4):
p = Process(target=work)
p.start()
p_list.append(p)
for p in p_list:
p.join()
print('总耗时:%s' % (time.time() - start_time))
# 多线程总耗时:3.0031137466430664
# 多进程总耗时:3.178115129470825
结论:
在进行IO密集型任务是多线程更好
死锁现象
锁不能轻易使用并且以后我们也不会在自己去处理锁都是用别人封装的工具
“死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通
信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称
系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。”
# from multiprocessing import Lock
from threading import Thread, Lock
import time
mutexA = Lock()
mutexB = Lock()
class MyThread(Thread):
def run(self):
self.f1()
self.f2()
def f1(self):
mutexA.acquire()
print(f'{self.name}抢到了A锁')
mutexB.acquire()
print(f'{self.name}抢到了B锁')
mutexB.release()
mutexA.release()
def f2(self):
mutexB.acquire()
print(f'{self.name}抢到了B锁')
time.sleep(2)
mutexB.acquire()
print(f'{self.name}抢到了A锁')
mutexA.release()
mutexB.release()
for i in range(20):
t = MyThread()
t.start()
信号量(了解)
'''
在多线程编程中,为了防止不同的线程同时对一个公用的资源(比如
全部变量)进行修改,需要进行同时访问的数量(通常是1)。信号量
同步基于内部计数器.
每调用一次acquire(),计数器减1;
每调用一次release(),计数器加1.当计数器为0时,acquire()调用被阻塞。
'''
信号量在不同的知识体系中 展示出来的功能是不一样的
eg:
在并发编程中信号量意思是多把互斥锁
在django框架中信号量意思是达到某个条件自动触发特定功能
"""
如果将自定义互斥锁比喻成是单个厕所(一个坑位)
那么信号量相当于是公共厕所(多个坑位)
"""
代码展示
from threading import Thread,Semaphore
import time
import random
# 只要是跟锁相关的几乎都不会让我们自己去写 后期还是用模块
sp = Semaphore(5) # 创建一个
def task(name):
sp.acquire() # 抢锁
print(f'{name}正在蹲坑')
time.sleep(random.randint(1,5))
sp.release() # 放锁
for i in range(30):
t = Thread(target=task,args=(i,))
t.start()
event事件(了解)
子线程的运行可以由其他子线程决定
'''
原理:
事件event中有一个全局内置标志Flag,值为True或者False。使用
wait()函数的线程会处于阻塞状态,此时Flag指为False,直到有其他线
程调用set()函数让全局标志Flag置为True,其阻塞的线程立刻恢复运
行,还可以用isSet()函数检查当前的Flag状态.
'''
Event几种方法:
event.isSet():返回event的状态值;
event.wait():如果 event.isSet()==False将阻塞线程;
event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
event.clear():恢复event的状态值为False。
举例:
from threading import Thread, Event
import time
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(10):
t = Thread(target=car, args=(f'AE86--{i}',))
t.start()
进程池与线程池(重点)
理论:
'''
池子使用来限制并发的任务数目,限制我们的计算机在一个自己可承
受的范围内去并发地执行任务
当并发的任务数远远超过了计算机的承受能力时,即无法一次性开启
过多的进程数或线程数时就应该用池的概念将开启的进程数或线程数
池子内什么时候装进程:并发的任务属于计算密集型
池子内什么时候装线程:并发的任务属于IO密集型
如果超出了从池子里面最大进程或线程数,则原地等待
强调:进程池和线程池其实降低了程序的运行效率,但是保证了硬件
的安全
不能无限的开进程,不能无限的开线程
最常用的就是开进程池,开线程池。其中回调函数非常重要
回调函数其实可以作为一种编程思想,谁好了谁就去调用
'''
线程池代码演示
# 阿巴阿巴阿巴
import time
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
# 线程池
pool = ThreadPoolExecutor(5) # 线程池线程数默认是CPU个数的五倍,也可以自定义
'''上面的代码执行之后就会立刻创建五个等待工作的线程'''
# 进程池
pool = ProcessPoolExecutor(5) # 进程池默认是CPU个数,也可以自定义
'''上面的代码执行之后就会立刻创建五个等待工作的进程'''
def task(n):
time.sleep(2)
print(n)
return f'任务的执行结果{n**2}'
#
def func(*args, **kwargs):
# print(args, kwargs)
print(args[0].result())
for i in range(20):
# pool.submit(task,i) # 朝线程池中提交任务(异步)
# res = pool.submit(task,i)
# print(res.result()) # 同步提交任务(获取任务的返回值)
'''不应该主动等待结果,应该让异步提交自动提醒>>>:异步回调机制'''
pool.submit(task, i).add_done_callback(func)
'''add_done_callback重要任务有结果了,就会自动调用括号内的返回值'''
#############################################
###############必须写在main方法里面##############
if __name__ == '__main__':
for i in range(20):
'''不应该主动等待结果,应该让异步提交自动提醒>>>:异步回调机制'''
pool.submit(task, i).add_done_callback(func)
'''add_done_callback重要任务有结果了,就会自动调用括号内的返回值'''
#####################################################
协程
概念
'''
1、协程:
单线程实现并发
在应用程序里控制多个任务的切换+保存状态
优点:
应用程序级别速度要远远高于操作系统的切换
缺点:
多个任务一旦有一个阻塞没有切,整个线程都阻塞在原地
该线程内的其他的任务都不能执行了
一旦引入协程,就需要检测单线程下所有的IO行为,
实现遇到IO就切换,少一个都不行,以为一旦一个任务阻塞了,整个线程就阻塞了,
其他的任务即便是可以计算,但是也无法运行了
2、协程序的目的:
想要在单线程下实现并发
并发指的是多个任务看起来是同时运行的
并发=切换+ 保存状态
'''
基本使用
from gevent import monkey;monkey.patch_all() # 固定编写 用于检测所有的IO操作
from gevent import spawn
import time
def play(name):
print(f'{name}play 1')
time.sleep(5)
print(f'{name}play 2')
def eat(name):
print(f'{name}eat 1')
time.sleep(3)
print(f'{name}eat 2')
start_time = time.time()
g1 = spawn(play, 'tuzi')
g2 = spawn(eat,'jason')
g1.join()
g2.join()
print('总耗时:', time.time() - start_time) # 正常串行肯定是8s+
# 总耗时: 5.031070947647095
协程实现TCP并发
# 服务端
from gevent import monkey;monkey.patch_all()
from gevent import spawn
import socket
def communication(sock):
while True:
data = sock.recv(1024) # IO操作
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)
g1 = spawn(get_server)
g1.join()
# 客户端
from gevent import monkey;monkey.patch_all()
from threading import Thread, current_thread
import socket
def get_client():
client = socket.socket()
client.connect(('127.0.0.1', 8080))
count = 0
while True:
msg = f'{current_thread().name, count} say hello'
count += 1
client.send(msg.encode('utf8'))
data = client.recv(1024)
print(data.decode('utf8'))
# 创建进程
for i in range(100):
t = Thread(target=get_client)
t.start()
结论
'''
终极结论
python可以通过开设多进程 在多进程下开设多线程 在多线程使用协程
从而让程序执行的效率达到极致!!!
但是实际业务中很少需要如此之高的效率(一直占着CPU不放)
因为大部分程序都是IO密集型的
所以协程我们知道它的存在即可 几乎不会真正去自己编写
"""