一、验证GIL的存在
from threading import Thread
money = 100
def task():
global money # 修改主线程的money
money -= 1 # 每次运行money - 1 没有io操作直接运行
t_list = [] # 设置一个空列表存放线程
for i in range(100):
t = Thread(target=task)
t.start() # 如果所有线程同时运行则全部拿到100 结果则是 99
t_list.append(t) # [线程1 线程2 线程3 ... 线程100]
for t in t_list:
t.join()
print(money) # 等待所有的线程运行结束 查看money是多少
输出结果: 0
二、验证GIL的特点
from threading import Thread
import time
money = 100
def task():
global money
tmp = money # 当GIL进入IO操作时会自动释放GIL互斥锁
time.sleep(0.1) # 释放之后 后面的99个进程都能拿到GIL
money = tmp - 1 # 都拿到得数据都是100 减去1 还是 99
t_list = [] # 设置一个空列表存放线程
for i in range(100):
t = Thread(target=task)
t.start() # 如果所有线程同时运行则全部拿到100 结果则是 99
t_list.append(t) # [线程1 线程2 线程3 ... 线程100]
for t in t_list:
t.join()
print(money) # 等待所有的线程运行结束 查看money是多少
输出结果: 99
'''
所以我们得出结论 GIL不会影响程序层面的数据 也不会保证它的修改是安全的
如果想要保证我们得加上 互斥锁!! (互斥锁具体看上一遍文章)
'''
解决方法:
from threading import Thread, Lock
import time # 导入互斥锁模块
money = 100
mutex = Lock() # 设置互斥锁
def task():
mutex.acquire() # 抢互斥锁锁
global money
tmp = money # 当GIL进入IO操作时会自动释放GIL互斥锁
time.sleep(0.1) # 释放之后 后面的99个进程都能拿到GIL
money = tmp - 1 # 都拿到得数据都是100 减去1 还是 99
mutex.release() # 释放互斥锁
t_list = [] # 设置一个空列表存放线程
for i in range(100):
t = Thread(target=task)
t.start() # 如果所有线程同时运行则全部拿到100 结果则是 99
t_list.append(t) # [线程1 线程2 线程3 ... 线程100]
for t in t_list:
t.join()
print(money) # 等待所有的线程运行结束 查看money是多少
三、死锁现象
到现在我们已掌握了互斥锁的使用 先抢锁再释放锁 但是实际项目很少用
mutexA = Lock() # 类名加括号每执行一次就会产生一个新的对象A
mutexB = Lock() # 类名加括号每执行一次就会产生一个新的对象B
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锁') # 这时候A锁还没有释放 所以只有第一个人抢得到b锁
mutexB.release()
print(f'{self.name}释放了B锁') # 释放b锁 但是所有人还在外面没人抢得到
mutexA.release()
print(f'{self.name}释放了A锁') # 这时候释放A锁 第二个人就可以抢到A锁了
def func2(self):
mutexB.acquire()
print(f'{self.name}抢到了B锁') # 第一个人拿到 B锁
time.sleep(1) # 进入IO操作
mutexA.acquire()
print(f'{self.name}抢到了A锁') # 这个时候发生了问题 目前A锁还没有释放!
mutexA.release()
print(f'{self.name}释放了A锁') # 所以BUG
mutexB.release()
print(f'{self.name}释放了B锁')
for i in range(10):
t = MyThread()
t.start()
'''输出结果: A>B>B>A>B>A 这就是死锁现象卡在那'''
四、信号量
信号量本质也是互斥锁 只不过他是多把互斥锁(自己可以设置数量)
信号量在不同的知识体系中 意思是有区别的
在并发编程中 信号量就是多把互斥锁
而在Djiango中 信号量是指达到了某个条件自动触发
我们之前使用Lock产生的是单把锁 信号量相当于一次性创建了N把锁
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)) # 随时等待睡眠1——3秒
sp.release() # 释放锁
for i in range(10): # 产生10个子进程
t = MyThread()
t.start()
五、event事件
event主要是让子进程与子线程之间可以彼此可以等待对方
子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(10):
t = Thread(target=car, args=('LebronJames%s' % i,))
t.start()
六、进程池和线程池
我们在实际应用中是不是可以无限制的开进程和线程?
肯定是不可以的 会造成内存溢出受限于硬件水平
我们在开设多进程或者多线程的时候 还需要考虑硬件的承受范围
池
降低程序的执行效率 保证计算机硬件的安全
进程池
提前创建好固定个数的进程供程序使用 后续不会再创建
线程池
提前创建好固定个数的线程供程序使用 后续不会再创建
# 线程
from concurrent.futures import ThreadPoolExecutor
from threading import current_thread
import os
import time
pool = ThreadPoolExecutor(5) # 固定产生五个线程
def task(): # 括号内可以加参
print(current_thread().name) # 打印线程名称
time.sleep(1)
for i in range(10): # submit(函数名,实参1,实参2,...)
pool.submit(task) # 可以跟着后面传参数逗号隔开
输出结果:ThreadPoolExecutor-0_0 ThreadPoolExecutor-0_1
ThreadPoolExecutor-0_2 ThreadPoolExecutor-0_3
ThreadPoolExecutor-0_4 ThreadPoolExecutor-0_0
ThreadPoolExecutor-0_2 ThreadPoolExecutor-0_3
ThreadPoolExecutor-0_1 ThreadPoolExecutor-0_4
'''十个线程 都是相同的不会有新的这就是设置的五个线程'''
# 进程
from concurrent.futures import ProcessPoolExecutor
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(10):
"""异步回调:异步任务执行完成后有结果就会自动触发该机制"""
pool.submit(task, 123).add_done_callback(func)
输出结果:67697 67696 67698 67699 67700
67696 func (<Future at 0x7f7c683816a0 state=finished returned str>,) {} 返回的结果
67697 func (<Future at 0x7f7c683757c0 state=finished returned str>,) {} 返回的结果
67698 func (<Future at 0x7f7c68381b20 state=finished returned str>,) {} 返回的结果
......
七、协程
我们都知道进程是资源单位
线程是执行单位
协程是单线程下实现并发(效率极高)
在代码层面欺骗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() # 8.015673160552979秒
s1 = spawn(func1) # 检测代码 一旦有IO自动切换(执行没有io的操作 变向的等待io结束)
s2 = spawn(func2) # 两个之间一直切换让CPU感觉一直在运行代码
s1.join() # 这个时候进入IO一直切换
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) # 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) # 一旦有用户进来就启动 如果没有IO操作等着
s1 = spawn(get_server) # 这样就好了 平凡的切换等着用户进来
s1.join()
客户端
import socket
from threading import Thread, current_thread
def get_client():
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
client.send(f'hello baby {current_thread().name}'.encode('utf8'))
data = client.recv(1024)
print(data.decode('utf8'))
for i in range(1000): # 一人接待1000人!!!
t = Thread(target=get_client)
t.start()
技术小白记录学习过程,有错误或不解的地方请指出,如果这篇文章对你有所帮助请
点点赞收藏+关注
谢谢支持 !!!