-
GIL与普通互斥锁区别
-
验证多线程是否有用
-
死锁现象
-
信号量与event事件
-
进程池与线程池
-
协程
-
GIL与普通互斥锁区别
# 判断GIL的存在
from threading import Thread
money = 100
def task():
global money
money -= 1
for i in range(100):
t = Thread(target=task)
t.start()
print(money)
# 0
# 通过验证不同数据加不同锁
from threading import Thread, Lock
import time
money = 100
mutex = Lock()
def task():
global money
mutex.acquire() # 加锁
tmp = money
time.sleep(0.2)
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) # 等待线程运行完毕再打印
# 0
"""
GIL是一个纯理论知识, 在实际工作中不同考虑它的存在,
GIL作用面很窄, 仅限于解释器级别
"""
"""
前提:
CPU的个数:单个、多个
任务的类型:IO密集型、计算密集型
"""
# 单个CPU时
多个IO密集型任务
多线程: 浪费资源,无法利用多个CPU
多线程: 节省资源,切换 + 并保存状态
多个计算密集型任务
多进程: 耗时更长, 创建进程的消耗 + 切换消耗
多线程: 耗时较短, 切换消耗
# 多个CPU时
多个IO密集型任务
多进程: 浪费资源, 多个CPU无用武之地
多线程: 节省资源, 切换 + 保存状态
多个计算密集型任务
多进程: 利用多核, 速度快
多线程: 速度较慢
结论: 多进程和多线程都有具体的应用场景, 尤其是多线程并不是没有用
# 多进程
from multiprocessing import Process
import time
def work():
res = 1
for i in range(1, 10000):
res *= i
if __name__ == '__main__':
# print(os.cpu_count()) # 8 可以获取计算机的CPU个数
start_time = time.time()
p_list = []
for i in range(8):
p = Process(target=work)
p.start()
p_list.append(p)
for p in p_list:
p.join()
print('总耗时:%s' % (time.time() - start_time))
总耗时:0.19758033752441406
# 多线程
from threading import Thread
from multiprocessing import Process
import os
import time
def work():
res = 1
for i in range(1, 10000):
res *= i
if __name__ == '__main__':
# print(os.cpu_count()) # 8 可以获取计算机的CPU个数
start_time = time.time()
p_list = []
for i in range(8):
p = Process(target=work)
p.start()
p_list.append(p)
for p in p_list:
p.join()
t_list = []
for i in range(8):
t = Thread(target=work)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print('总耗时:%s' % (time.time() - start_time))
总耗时:0.322171688079834
"""
计算密集型
多进程
0.19758033752441406
多线程
0.322171688079834
所以多进程好
"""
# 多线程
from threading import Thread
import time
def work():
time.sleep(2)
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()
print('总耗时:%s' % (time.time() - start_time))
# 总耗时:0.011942148208618164
# 多进程
def work():
time.sleep(2)
if __name__ == '__main__':
start_time = time.time()
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))
# 总耗时:0.5508136749267578
"""
IO密集型
多线程
0.011942148208618164
多进程
0.5508136749267578
结论:
多线程更好
"""
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)
mutexA.acquire()
print(f'{self.name} 得到了A锁')
mutexA.release()
mutexB.release()
for i in range(20):
t = MyThread()
t.start()
# 出现死锁现象
"""
锁不能轻易使用并且我们也不会自己去处理锁,因为都是拿别人封装的工具
"""

# 信号量在不同的体系里面, 展现出来的功能是不一样
在并发编程中信号量意思就是多把互斥锁
在django框架中信号量意思就是达到某个条件自动触发特定的功能
"""
将自定义互斥锁比喻成豪华个人卫生间
信号量就相当于公共厕所,里面有多个位子
"""
from threading import Thread, Semaphore
import time
import random
sp = Semaphore(5) # 自定义五个带门锁的坑位的厕所
def task(name):
sp.acquire() # 抢锁
print('%s正在蹲坑' % name)
time.sleep(random.randint(1, 5))
sp.release() # 放锁
for i in range(1, 21):
t = Thread(target=task, args=('小赤佬%s号'% i, ))
t.start()

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=('伞兵%s'%i, ))
t.start()
# 结果显示,前面先在等红灯, 然后时间一到就开始跑了

"""
服务端必备的三要素
1、24小时不间断提供服务
2、固定的IP和port
3、支持高并发
"""
TCP服务端实现并发
多进程: 来一个人客户端开一个进程
多线程: 来一个客户端开一个线程
计算机硬件是有物理极限的,我们不可能无限制的创建进程和线程
解决方法:
""" 池:
保证计算机硬件安全的情况下提升程序的运行效率
进程池:
提前创建好固定数量的进程, 后续反复使用这些进程
线程池:
提前创建好固定数量的线程,后面反复使用线程
当任务超出了池子里面的最大进程或线程数, 就原地等待
进程池和线程池其实降低了程序的运行效率, 但是它保证了硬件的安全
"""
# 代码
# 线程池
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time
from threading import current_thread
def task(n):
time.sleep(3)
print(n)
print(current_thread().name) # 线程最大数为4
def func(*args, **kwargs):
print(args, kwargs)
pool = ThreadPoolExecutor(5) # 线程池线程数默认是CPU的五倍,也可以自定义
'''让异步提交自动提醒>>>:异步回调机制'''
for i in range(10):
pool.submit(task, i).add_done_callback(func)
"""
add_done_callback当任务有结果的时候, 自动调用括号内的函数处理
"""
# 进程池
pool = ProcessPoolExecutor(5) # 进程池进程数默认是CPU个数 也可以自定义
'''代码执行之后就会立刻创建五个等待工作的进程'''
pool.submit(task, i).add_done_callback(func)
"""
进程: 资源单位
线程: 执行单位
协程: 单线程下实现并发
并发的概念: 切换 + 保存状态
对于操作系统来认识进程和线程
协程就是自己通过代码来检测程序的IO操作并自己处理 让CPU感觉不到IO的存在, 从而最大幅度的占用CPU
"""
# 固定编写, 用于检测所有的IO操作
from gevent import monkey; monkey.patch_all()
from gevent import spawn
import time
def play(name):
print('%s 在玩娃娃'% name)
time.sleep(5)
print('%s 在玩皮球'% name)
def eat(name):
print('%s 在吃龙虾' % name)
time.sleep(3)
print('%s 在吃螺蛳粉' % name)
start_time = time.time()
g1 = spawn(play, 'owen')
g2 = spawn(eat, 'owen')
g1.join()
g2.join()
print('总耗时:', time.time() - start_time) # 正常串行是8s+
# 结果为
owen 在玩娃娃
owen 在吃龙虾
owen 在吃螺蛳粉
owen 在玩皮球
总耗时: 5.03695273399353 # 代码控制切换
# 服务端
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 threading import Thread, current_thread
import socket
def get_client():
client = socket.socket()
client.connect(('127.0.0.1', 8888))
count = 0
while True:
msg = '%s 芜湖 %s'%(current_thread().name, count)
count += 1
client.send(msg.encode('utf8'))
data = client.recv(1024)
print(data.decode('utf8'))
# 创建多线程
for i in range(200):
t = Thread(target=get_client())
t.start()
"""
python可以通过开设多进程, 在多进程开设多线程在多线程使用协程
实际很少需要那么高的效率
"""
