python进程与线程之间的区别
目录
- 一.GIL与普通互斥锁的区别
- 1.GIL锁
- 2.GIL与互斥锁
- 二.验证多线程的作用
- 1.多核计算密集型任务多线程
- 2.多核计算密集型任务多进程
- 结论
- 3.单核IO密集型任务多线程
- 4.单核IO密集型任务多进程
- 结论
- 三.死锁现象
- 死锁现象
- 四.信号量
- 1.多把互斥锁
- 五.event事件
- 1.子线程控制子线程运行
- 六.进程池与线程池
- 七.协程
- 1.协程演练
- 八.基于协程实现TCP服务端并发
- 1.服务端
- 2.客户端
一.GIL与普通互斥锁的区别
# 1.首先什么是GIL
GIL是全局解释器锁 解决解释器中多个线程资源竞争问题(多个子线程在系统资源竞争
都在等待对象某个部分资源解除占用状态.结果谁也不愿意先解锁 然后互相等待 导致
程序无法进行下去)
# 2.使用多线程时
当我们使用多线程的时候 每一个进程中只有一个GIL锁那么这个线程中谁拿到了这个GIL锁
那么谁就可以使用CPU(多个进程内有多个GIL锁.但每个进程中只有一个GIL锁)所以当python用
Cpython作为解释器的时候,多线程就不是真正意义上的多线程,属于一个伪并发的多线程
# 3.解释器Cpython(使用率挺高)
在Cpython解释器下 GIL导致了同一进程下的多个线程无法利用多核
# 4.GIL与普通互斥锁的区别
GIL锁:保证了同一时刻 只有有一个线程可以使用到CPU
互斥锁:多线程时,保证修改共享数据时有序的修改,不会产生数据修改混乱
1.GIL锁
from threading import Thread
choice = 50
def choice_task():
global choice
choice -= 1
# 创建50个线程 在内存也就是一个进程内同时启动50个线程
for i in range(50):
q = Thread(target=choice_task)
q.start()
print(choice)
"""
总结下:GIL的全局解释器锁
解决了多个线程纠纷资源问题
结果为:0 释放一个接收一个重复50次
"""
2.GIL与互斥锁
from threading import Thread,Lock
import time
choice = 100
t = Lock()
def choice_task():
global choice
t.acquire() # 加锁
i = choice
time.sleep(0.1) # io 操作
choice = i - 1
t.release() # 释放锁
q_list = []
for j in range(100):
q = Thread(target=choice_task)
q.start()
q_list.append(q)
for w in q_list:
q.join()
print(choice)
"""
个人总结:
当线程碰到io操作的时候那么python解释器锁已经没做用了
因为io操作结束后自动解除锁 子线程就又会从新回到就绪太
创建100个线程进程同时进行 100-1
结果是99
如果加上join那么主线程会等子线程结束后执行那么结果为0
添加for循环打印的话没有join方法那么就会出现 99个 100-1
因为主线程未等待子线程直接打印添加列表内的数值
区分GIL锁 与 互斥锁 根据不同的数据使用不同的锁
"""
二.验证多线程的作用
通过CPU的个数(核) 和 任务的数据类型
# 单核(CPU)
# 多个IO密集型任务
多进程:浪费资源 无法利用多个CPU
多线程:节省资源 切换 保存
# 多个计算密集型任务
多进程:损耗时间更长长 创建损耗和切换损耗
多线程:损耗时间较短短 切换损耗较短
# 多核(CPU)
# 多余个IO密集型任务
多进程:浪费资源 多核无用户处
多线程:节省资源 切换 保存
# 多个计算密集型任务
多进程:完美利用多核优势 速度更快
多线程:速度相比之下比较缓慢
1.多核计算密集型任务多线程
from threading import Thread
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()) # 查看当前计算机CPU个数
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()
print('总耗时:%s' % (time.time() - start_time))
结果为 多线程:0.28725099563598633
2.多核计算密集型任务多进程
from threading import Thread
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()) # 查看当前计算机CPU个数
start_time = time.time()
t_list = []
for i in range(4):
t = Process(target=work) # Process进程 Thread线程区分
t.start()
t_list.append(t)
for t in t_list:
t.join()
print('总耗时:%s' % (time.time() - start_time))
结果:多进程 0.08273792266845703
结论
在计算密集型任务下 多进程 要比 多线型快不少
但如果你计算机核少于4核或等于4核那么可能会恰恰相反
3.单核IO密集型任务多线程
from threading import Thread
import time
def work():
time.sleep(2) # 纯IO操作
if __name__ == '__main__':
start_time = time.time()
p_list = []
for i in range(10):
p = Thread(target=work)
p.start()
p_list.append(p)
for p in p_list:
p.join()
print('总耗时:%s' % (time.time() - start_time))
结果 总耗时:2.0170059204101562 并没等子线程循环反应过来 就已经结束掉了
4.单核IO密集型任务多进程
from multiprocessing import Process
import time
def work():
time.sleep(2) # 纯IO操作
if __name__ == '__main__':
start_time = time.time()
p_list = []
for i in range(10):
p = Process(target=work)
p.start()
p_list.append(p)
for p in p_list:
p.join()
print('总耗时:%s' % (time.time() - start_time))
结果:总耗时:总耗时:4.950006723403931 等子进程打印完后才才结束
结论
在多核IO密集型任务多进程与多线程 对比下 多线程快于多进程
总体来说是多线程比多进程快
三.死锁现象
# 什么是死锁现象 频繁的加锁 解锁
死锁现象
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(2):或(100) 循环减少不会死锁
t = MyThread()
t.start()
结果: 结果就是卡在打印界面 运行的太快锁没来得及上就又解锁 导致卡主 由此可以引出锁不能轻易使用
四.信号量
信号量在不同的知识体系中 展示出来的功能是不一样的
# 1.在并发编程中
信号量意思是多把互斥锁
# 2.在django框架中
信号量意思是达到某个条件自动触发特定功能
1.多把互斥锁
from threading import Thread, Semaphore
import time
import random
sp = Semaphore(5) # 这个方法是可以管理有几个锁
def task(name):
sp.acquire() # 抢5个锁
print('%s次' % name)
time.sleep(random.randint(1, 5))
sp.release() # 放锁
for i in range(1, 10):
t = Thread(target=task, args=('学生上课打瞌睡%s' % i, ))
t.start()
结果: 学生上课打瞌睡1次
学生上课打瞌睡2次
学生上课打瞌睡3次
学生上课打瞌睡4次
学生上课打瞌睡5次
学生上课打瞌睡6次
学生上课打瞌睡7次
学生上课打瞌睡9次
学生上课打瞌睡8次
五.event事件
event事件:子线程运行可以由子线程来决定
# 其实可以通过Queue队列方法实现
1.子线程控制子线程运行
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()
六.进程池与线程池
# 服务端必备的三要素:想象为游戏
不轻易断开链接:除了更新想什么时候玩都型
固定ip与port :登陆账号密码就能随便玩
支持高并发 : 一堆朋友一起玩
# TCP服务端实现并发
多进程:一个客户端对应一个进程
多线程:一个客户端对应一个线程
# 进程与线程'池'
池:保证计算机硬件安全的情况下提升程序的运行效率
进程池:提前创建好固定数量的进程 后续反复使用这些进程
线程池:提前创建好固定数量的线程 后续反复使用这些线程
"""
如果任务超出了池子里面的最大进程或线程数 则原地等待
进程池和线程池其实降低了程序的运行效率 但是保证了硬件的安全.
"""
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time
from threading import current_thread
pool = ThreadPoolExecutor(2) # 线程池线程数默认是CPU个数的五倍,也可以自定义
# """创建5个等待工作的线程"""
def task(n):
time.sleep(2)
print(n)
# print(current_thread().name)
return '啥也不是' # 获取人物的返回值,否则就是None
def run(*args,**kwargs):
print(args[0].result()) # 通过接收方式接收返回值
for i in range(5):
pool.submit(task, i).add_done_callback(run)
七.协程
# 协程:单线程下实现并发(切换 保存)
# 进程:资源单位
# 线程:执行单位
#协程就是自己通过代码来检测程序的IO操作并自己处理 让CPU感觉不到IO的存在从而最大幅度的占用CPU
1.协程演练
from gevent import monkey;monkey.patch_all() # 固定编写 用于检测所有的IO操作
from gevent import spawn
import time
def play(name):
print('%s play 1' % name)
time.sleep(5)
print('%s play 2' % name)
def eat(name):
print('%s eat 1' % name)
time.sleep(3)
print('%s eat 2' % name)
start_time = time.time()
g1 = spawn(play, 'jason')
g2 = spawn(eat, 'jason')
g1.join() # 等待检测任务执行完毕
g2.join() # 等待检测任务执行完毕
print('总耗时:', time.time() - start_time) # 正常串行肯定是8s+
# 5.00609827041626 代码控制切换
八.基于协程实现TCP服务端并发
1.服务端
from gevent import monkey;monkey.patch_all()
from gevent import spawn
import socket
def conmunication(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',9999))
server.listen(5)
while True:
sock, add = server.accept() # IO操作
spawn(conmunication,sock)
g1 = spawn(get_server)
g1.join()
2.客户端
from threading import Thread,current_thread
import socket
def get_client():
client = socket.socket()
client.connect(('127.0.0.1',9999))
count = 0
while True:
msg = '%s say hello %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()
spawn消耗资源,对CPU有一定的负荷
python可以通过开设多进程,在多进程下开设多线程,在多线程使用协程,从而让程序执行的效率达到极致
但是实际业务中很少需要如此之高的效率(一直占着CPU),因为大部分程序都是IO密集型