验证GIL
GIL的存在
from threading import Thread
num = 100
def task():
global num
num -= 1
t_list = []
for i in range(100):
t = Thread(target=task)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(num)
"""
大致流程为:
一百个进程抢一把锁,抢到之后执行进程操作,全局中的num每抢一次就自减一,
当前进程运行完毕。其他进程又进行此操作,随后重复该操作
"""
GIL的特点
from threading import Thread
import time
num = 100
def task():
global num
tmp = num
time.sleep(0.1)
num = tmp - 1
t_list = []
for i in range(100):
t = Thread(target=task)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(num)
"""
大致流程:
一百个进程抢一把锁,抢到之后执行进程操作,全局中的num每抢一次先进行
IO操作,放出抢到的锁,其他进程又可以进行抢锁。每个程序睡眠结束后自减1,
没有锁,所以每次不会继承上一次的进程的num,得到的数仍为num-1之后的数。
"""
"""
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)
for t in t_list:
t.join()
print(money)
验证python的多线程有什么用
-
- 单个cpu的IO密集型
- 多进程
申请额外的空间,消耗的资源更多
- 多线程
资源消耗相比较少,通过多道技术
-
- 单个cpu的计算密集型
- 多进程
申请额外的空间 消耗更多的资源
- 多线程
资源消耗相比较少,通过多道技术
-
- 多个cpu的IO密集型
-
- 多个cpu的计算密集型
- 多进程
总耗时(单个进程的耗时)
- 多线程
总耗时(多个进程的综合)
- 此类型中的多进程优势很大
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__':
start_time = time.time()
p_list = []
for i in range(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
"""
死锁现象
- 为什么会出现死锁现象?
- 在抢锁的时候 通过一些IO操作形成了闭环,互拿对方的锁
Lock() # 类名加括号每执行一次就会产生一个新的对象
- 实例分析
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()
信号量
- 本质
- 如何使用
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()
event事件
子进程或者子线程之间可以彼此等待彼此
eg:子A运行到某一个代码位置后发信号告诉子B开始运行
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()
进程池与线程池
- 为什么要有进程/线程池呢?
- 为了保证计算机硬件的安全,但是代价是降低了程序的执行效率。我们需要考虑硬件的承受能力
- 进程池提前创建好固定个数的进程供程序使用,后续不会再创建
- 线程池提前创建好固定个数的线程供程序使用,后续不会再创建
实操:
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
from threading import current_thread
import os
import time
pool = ProcessPoolExecutor(5)
def task(n):
print(os.getpid())
time.sleep(1)
return '返回结果'
def func(*args, **kwargs):
print('func', args, kwargs)
print(args[0].result())
if __name__ == '__main__':
for i in range(20):
pool.submit(task, 123).add_done_callback(func)
协程及协程实操
协程
- 是程序员自己定义出出来的一种方法,可以在单线程下实现并发,效率极高
- 其实就是戏法,让cpu误以为没有IO操作。实际上IO操作被我们自己写的代码检测,有的话立刻让代码执行别的。
- 核心:自己写代码完成切换+保存状态
from gevent import monkey;monkey.patch_all()
from gevent import spawn
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()
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()