GIL,多线程有用吗,死锁现象,信号量,event事件,进程池和线程池,协程
-
GIL
-
GIL的本质
-
验证GIL的存在
-
验证GIL的特点和互斥锁
-
-
验证python多进程和多线程那个更有用(需要分情况)
-
单个cpu前提下python多进程和多线程那个更有用
-
多个cpu前提下python多进程和多线程那个更有用
-
-
死锁现象
-
信号量
-
event事件(Event模块)
-
进程池(ProcessPoolExecutor)和线程池(ThreadPoolExecutor)
-
何为池?
-
池中的方法名和作用
-
-
协程
-
小总结
-
如何不断的提升程序的运行效率
-
1.GIL
1.GIL的本质:
GIL的本质就是互斥锁,而互斥锁的本质都是将并发编程串行,从而控制同一时间内共享的数据只能被一个任务所修改,进而保证数据安全
2.验证GIL的存在
验证GIL的存在的代码
from threading import Thread
money = 100
def task():
global money
money -= 1
t_list = []
for i in range(100):
t = Thread(target=task)
t.start()
t_list.append(t) # [线程1 线程2 线程3 ... 线程100]
for t in t_list:
t.join()
# 等待所有的线程运行结束 查看money是多少
print(money) # 0
这里呢是一个串行,从结果为0我们可以推到出首先创建了100个线程,之后100个线程同时去抢money谁抢到了就是谁的,其他的一直等待,等到刚才强盗money的线程执行结束然后在一起去抢
3.验证GIL的特点和互斥锁
验证GIL的特点和互斥锁的代码
from threading import Thread
import time
money = 100
def task():
global money
tmp = money
time.sleep(0.1)
money = tmp - 1
t_list = []
for i in range(100):
t = Thread(target=task)
t.start()
t_list.append(t) # [线程1 线程2 线程3 ... 线程100]
for t in t_list:
t.join()
# 等待所有的线程运行结束 查看money是多少
print(money) # 99
这里是一个异步这里我们让代码睡眠0.1s,而此时就会进入IO操作,其他的线程也会执行
验证GIL的特点和互斥锁的代码
from threading import Thread,Lock
import time
money = 100
mutex = Lock()
def task():
mutex.acquire()
global money
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) # [线程1 线程2 线程3 ... 线程100]
for t in t_list:
t.join()
# 等待所有的线程运行结束 查看money是多少
print(money) # 0
这里我们添加一个互斥锁,就将上面的异步转变为串行
2.验证python多进程和多线程那个更有用(需要分情况)
| 情况一 | 单个cpu还是多个cpu |
| 情况二 | IO密集型(代码中有IO操作)还是计算密集型(代码中没有IO操作) |
1.单个cpu前提下python多进程和多线程那个更有用
| IO密集型 | 多进程: 申请额外的空间 消耗更多资源 多线程: 消耗资源相对较少 通过多道技术 |
| 计算密集型 | 多进程: 申请额外的空间 消耗更多的资源(总耗时+申请空间+拷贝代码+切换) 多线程: 消耗资源相对较少 通过多道技术(总耗时+切换) |
这里可以总结出无论是IO密集型还是计算密集型都是多线程更有优势
2.多个cpu前提下python多进程和多线程那个更有用
| IO密集型 | 多进程: 总耗时(单个进程的耗时+IO+申请空间+拷贝代码) 多线程: 总耗时(单个进程的耗时+IO) |
| 计算密集型 | 多进程 总耗时(单个进程的耗时) 多线程 总耗时(多个进程的综合) |
这里可以看出多个cpu条件下IO密集型情况下是多线程更有优势
计算密集型情况下是多进程更有优势
所以他们都有自己的有点也有缺点
3.死锁现象
死锁指的是两个或两个以上的进程或线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去的状态。(解决方法是递归锁)
死锁现象的代码
"""
虽然我们已经掌握了互斥锁的使用
先抢锁 后释放锁
但是在实际项目尽量少用(你也不会用!!!)
"""
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):
t = MyThread()
t.start()
4.信号量
信号量的本质也是互斥锁,只不过信号量可以通过代码的形式(Semaphore模块)创建多把锁(Semaphore(5) 一次性产生五把锁)。
强调:
| 信号量在不同的知识体系中 意思可能有区别 1.在并发编程中 信号量就是多把互斥锁 2.在django中 信号量指的是达到某个条件自动触发(中间件) |
信号量有关代码
from threading import Thread, Lock, Semaphore
import time
import random
sp = Semaphore(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()
5.event事件(Event模块)
本质:子进程/子线程 之间可以彼此等待彼此
event事件代码
from threading import Thread, Event
import time
event = Event() # 类似于造了一个红绿灯
def light():
print('红灯亮着的 所有人都不能动')
time.sleep(3)
print('绿灯亮了 油门踩到底 给我冲!!!')
event.set()
def car(name):
print('%s正在等红灯' % name)
event.wait()
print('%s加油门 飙车了' % name)
t = Thread(target=light)
t.start()
for i in range(20):
t = Thread(target=car, args=('熊猫PRO%s' % i,))
t.start()
6.进程池(ProcessPoolExecutor)和线程池(ThreadPoolExecutor)
通过concurrent.futures模块导入池
何为池?
池:是保证计算机硬件安全的情况下提升程序的运行效率
| 进程池 |
提前创建好固定数量的进程 后续反复使用这些进程 |
| 线程池 | 提前创建好固定数量的线程 后续反复使用这些线程 |
注意:
1.如果任务超出了池子里面的最大进程或线程数 则原地等待
2.进程池和线程池其实降低了程序的运行效率 但是保证了硬件的安全!!!
池中的方法名和作用
| 方法名 | 作用 |
| add_done_callback | 异步回调机制 |
| shutdown | 关闭池 |
| result | 会返回**提交的任务最终返回的结果 ** |
| submit |
pool_p.submit(task,n=i)
|
进程池和线程池的代码
# submit(函数名,实参1,实参2,...)
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
from threading import current_thread
import os
import time
# pool = ThreadPoolExecutor(5) # 固定产生五个线程
pool = ProcessPoolExecutor(5) # 固定产生五个进程
def task(n):
# print(current_thread().name)
print(os.getpid())
# print(n)
time.sleep(1)
return '返回的结果'
def func(*args, **kwargs):
print('func', args, kwargs)
print(args[0].result())
if __name__ == '__main__':
for i in range(20):
# res = pool.submit(task,123) # 朝池子中提交任务(异步)
# print(res.result()) # 同步
# pool.submit(task, 123).add_done_callback(func)
"""异步回调:异步任务执行完成后有结果就会自动触发该机制"""
pool.submit(task, 123).add_done_callback(func)
7.协程(微线程)
协程不是计算机提供的,而是程序员人为创造的
目的:
协程是python中另外一种实现多任务的方式
| 进程:资源单位 线程:执行单位 协程:单线程下实现并发(效率极高) 在代码层面欺骗CPU 让CPU觉得我们的代码里面没有IO操作 实际上IO操作被我们自己写的代码检测 一旦有 立刻让代码执行别的 (该技术完全是程序员自己弄出来的 名字也是程序员自己起的) 核心:自己写代码完成切换+保存状态 |
小总结
- 进程是资源分配的单位
- 线程是操作系统调度的单位
- 进程切换需要的资源很最大,效率很低
- 线程切换需要的资源一般,效率一般
- 协程切换任务资源很小,效率高
- 多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中 所以是并发
如何不断的提升程序的运行效率
多进程下开多线程 多线程下开协程
协程代码
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
协程实现TCP服务端并发代码
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号