GIL全局解释锁,死锁,信号量,event事件,线程queue,TCP服务端实现并发
一、GIL全局解释锁
在Cpython解释器才有GIL的概念,不是python的特点
在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势。
1.GIL介绍
GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都是一样,都是将并发运行变成串行,以此来保证数据的安全性。用来阻止同一个进程下的多个线程的同时执行。保护不同的数据的安全,就应该加不同的锁。
每次执行python程序,都会产生一个独立的进程,每个py文件都会产生独立的python进程。在一个python进程中,不仅有这个文件的主线程和开启的其他线程,还有解释器开启的垃圾回收机制等解释器级别的线程。
GIL存在是因为Cpython解释器的内存管理不是线程安全的:(这个内存管理指的是垃圾回收机制)
#1 所有数据都是共享的,这其中,代码作为一种数据也是被所有线程共享的(运行的py文件代码以及Cpython解释器的所有代码)
例如:test.py定义一个函数work,在进程内所有线程都能访问到work的代码,于是我们可以开启三个线程然后target都指向该代码,能访问到意味着就是可以执行。 #2 所有线程的任务,都需要将任务的代码当做参数传给解释器的代码去执行,即所有的线程要想运行自己的任务,首先需要解决的是能够访问到解释器的代码。
综上所述:在同一进程中多个线程在执行代码的时候,多个线程先访问到解释器的代码,拿到执行权限,然后将代码交给解释器去执行。解释器的代码是所有线程共享的,所以垃圾回收线程也是可以可能访问到解释器的代码去执行,这就导致了一个问题,当线程刚刚创建了一个数字100,还没来得及和一个变量名绑定关系,这个时候如果垃圾回收机制来检测到了,会直接把没有绑定关系的数字100删除,考虑到这种问题,所以出现GIL全局解释锁,保证python解释器同一时间只能执行一个任务的代码。
2.GIL和Lock
GIL保护的是解释器级的数据,Lock保护用户自己的数据。保护不同的数据的安全,就应该加不同的锁。
分析:
#1.100个线程去抢GIL锁,即抢执行权限
#2. 肯定有一个线程先抢到GIL(暂且称为线程1),然后开始执行,一旦执行就会拿到lock.acquire()
#3. 极有可能线程1还未运行完毕,就有另外一个线程2抢到GIL,然后开始运行,但线程2发现互斥锁lock还未被线程1释放,于是阻塞,被迫交出执行权限,即释放GIL
#4.直到线程1重新抢到GIL,开始从上次暂停的位置继续执行,直到正常释放互斥锁lock,然后其他的线程再重复2 3 4的过程
GIL锁与互斥锁综合分析(重点!!!)
#互斥锁与join的区别 join是等待某个子线程或者子线程所有代码执行完毕,相当于锁住了整个代码
Lock只是锁住一部分操作数据的代码 两者之间是有很大差距的
3.GIL和多线程
有了GIL的存在,在同一时刻同一进程里面只有一个线程被执行。
那python的多线程没法利用多核的优势,是不是就没用了?
研究python的多线程是否有用需要分情况讨论:
假如有四个任务 计算密集型的情况下
单核情况下:开多个进程只会增加内存的开销,多线程开销更少,线程更节省资源
多核情况下:多核意味着并行计算,多进程可以把任务分散给每一个CPU,但是线程在一个进程中同一时刻只有一个线程执行用不上多核。进程更节省资源
假如有四个任务 I/O密集型的情况下
单核情况下:多进程增加资源消耗,而且进程的切换速度远不如线程,线程更节省资源
多核情况下:进程再多的核也解决不了I/O问题,都会进入阻塞态,而且还消耗资源,线程更节省资源
应用:多线程用于IO密集型:如socket,爬虫,web 多进程用于计算密集型:如金融分析
二、死锁现象和递归锁
所谓死锁:是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。如下就是死锁
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('\033[41m%s 拿到A锁\033[0m' %self.name) mutexB.acquire()
print('\033[42m%s 拿到B锁\033[0m' %self.name)
mutexB.release() mutexA.release() def func2(self):
mutexB.acquire()
print('\033[43m%s 拿到B锁\033[0m' %self.name)
time.sleep(2) mutexA.acquire()
print('\033[44m%s 拿到A锁\033[0m' %self.name)
mutexA.release() mutexB.release() if __name__ == '__main__':
for i in range(10):
t=MyThread()
t.start() '''
Thread-1 拿到A锁
Thread-1 拿到B锁
Thread-1 拿到B锁
Thread-2 拿到A锁
然后就卡住,死锁了
'''
解决方法使用递归锁:RLock。RLock可以被第一个抢到锁的人连续的acquire和release,每acquire一次,锁计数加1,每release一次,锁计数减1,只要锁的
计数不为0,其他线程都只能等待,等待该线程释放完所有锁。
三、信号量Semaphore
Semaphore 管理一个内置的计数器,每当调用acquire()是内置计数-1,调用release()是内置计数器+1,计数器不能小于0,当计数器为0时,acquire()将阻塞线程直到其他线程调用release()
"""
互斥锁:一个厕所(一个坑位)
信号量:公共厕所(多个坑位)
"""
from threading import Semaphore,Thread
import time
import random sm = Semaphore(5) # 造了一个含有五个的坑位的公共厕所 def task(name):
sm.acquire()
print('%s占了一个坑位'%name)
time.sleep(random.randint(1,3))
sm.release() for i in range(40):
t = Thread(target=task,args=(i,))
t.start() #同时创建了40个线程,5个坑位的公共厕所,开始有五个线程去占,睡不同时间之后,释放锁,同时其他线程拿到锁,最多只能有五个线程拿到锁。
信号量例子
四、event事件 让线程等待线程
from threading import Event,Thread
import time # 先生成一个event对象
e = Event() def light():
print('红灯正亮着')
time.sleep(3)
e.set() # 发信号
print('绿灯亮了') def car(name):
print('%s正在等红灯'%name)
e.wait() # 等待信号
print('%s加油门飙车了'%name) t = Thread(target=light) #线程运行light函数
t.start() for i in range(10):
t = Thread(target=car,args=('伞兵%s'%i,))
t.start() #线程先执行light函数,然后运行car函数,睡3秒,运行e.set()及下面的代码,同时,e.wait在等待信号,接收到信号之后也执行下面的代码。
五、线程queue
同一个进程下的多个线程本来就是数据共享 为什么还要用队列,因为队列是管道+锁,使用队列你就不需要自己手动操作锁的问题,因为锁操作不好极容易产生死锁现象。
import queue q = queue.Queue() #先进先出
q.put('hahha')
print(q.get()) #haha q = queue.LifoQueue() #last in first out 后进先出 和堆栈先进后出一样
q.put(1)
q.put(2)
q.put(3)
print(q.get()) #3 q = queue.PriorityQueue()
# 数字越小 优先级越高
q.put((10,'haha'))
q.put((100,'hehehe'))
q.put((0,'xxxx'))
q.put((-10,'yyyy'))
print(q.get()) #(-10, 'yyyy')
六、TCP服务端实现并发
将不同的功能尽量拆分成不同的函数拆分出来的功能可以被多个地方使用
1.将连接循环和通信循环拆分成不同的函数
2.将服务端通信循环做成多线程
服务端
import socket
from threading import Thread server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5) def talk(conn):
while True:
try:
data = conn.recv(1024)
if len(data) == 0:break
print(data.decode('utf-8'))
conn.send(data.upper())
except ConnectionResetError as e:
print(e)
break
conn.close() while True:
conn, addr = server.accept() # 监听 等待客户端的连接 阻塞态
t = Thread(target=talk,args=(conn,))
t.start()
客户端
import socket client = socket.socket()
client.connect(('127.0.0.1',8080)) while True:
client.send(b'hello')
data = client.recv(1024)
print(data.decode('utf-8'))
GIL全局解释锁,死锁,信号量,event事件,线程queue,TCP服务端实现并发的更多相关文章
- GIL 信号量 event事件 线程queue
GIL全局解释器锁 官方解释: In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple n ...
- 8.14 day32 TCP服务端并发 GIL解释器锁 python多线程是否有用 死锁与递归锁 信号量event事件线程q
TCP服务端支持并发 解决方式:开多线程 服务端 基础版 import socket """ 服务端 1.要有固定的IP和PORT 2.24小时不间断提供服务 3.能够支 ...
- 20191031:GIL全局解释锁
20191031:GIL全局解释锁 总结关于GIL全局解释锁的个人理解 GIl全局解释锁,本身不是Python语言的特性,而是Python语言底层的c Python解释器的一个特性.在其他解释器中是没 ...
- ~~并发编程(十一):GIL全局解释锁~~
进击のpython ***** 并发编程--GIL全局解释锁 这小节就是有些"大神"批判python语言不完美之处的开始 这一节我们要了解一下Cpython的GIL解释器锁的工作机 ...
- 子进程回收资源两种方式,僵尸进程与孤儿进程,守护进程,进程间数据隔离,进程互斥锁,队列,IPC机制,线程,守护线程,线程池,回调函数add_done_callback,TCP服务端实现并发
子进程回收资源两种方式 - 1) join让主进程等待子进程结束,并回收子进程资源,主进程再结束并回收资源. - 2) 主进程 “正常结束” ,子进程与主进程一并被回收资源. from multipr ...
- python并发编程-多线程实现服务端并发-GIL全局解释器锁-验证python多线程是否有用-死锁-递归锁-信号量-Event事件-线程结合队列-03
目录 结合多线程实现服务端并发(不用socketserver模块) 服务端代码 客户端代码 CIL全局解释器锁****** 可能被问到的两个判断 与普通互斥锁的区别 验证python的多线程是否有用需 ...
- GIL全局解释器锁-死锁与递归锁-信号量-event事件
一.全局解释器锁GIL: 官方的解释:掌握概念为主 """ In CPython, the global interpreter lock, or GIL, is a m ...
- 10 并发编程-(线程)-GIL全局解释器锁&死锁与递归锁
一.GIL全局解释器锁 1.引子 在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势 首先需要明确的一点是GIL并不是Python的特性,它是在实现Pyt ...
- python中的GIL(全局解释锁)多线程能够提升效率
预启动的时候,应用程序仍然会调用 OnLaunched 方法的,在 OnLaunched 方法调用之后,会马上发生 Suspending 事件,随后应用就会暂停. 我先基于develop主分支拉出一个 ...
随机推荐
- kuma 学习三 组件说明
当前官方已经提供了两种可选的运行模式 通用模式 kubernetes 模式 kuma 组件说明 kuma-cp kuma 的控制面板 kuma-dp kuma 的数据面板 enovy 提供sideca ...
- blockstack与Ethereum
https://github.com/blockstack https://bihu.com/article/1260288 blockstack是用户登录的认证中心,类似java中的OAuth2进行 ...
- SQL进阶-隐式类型转换
一.隐式类型转换 1.隐式类型转换 隐式类型转换: '; 程序可读性差,且依赖数据库的隐式转换规则,如果数据库升级,则程序可能无法正确执行: 有可能会导致索引失效: 有可能会导致意想不到的结果: 显式 ...
- Waring:not all local change may be shown due to an error:fatal
idea往Git上提交文件时提示 Waring:not all local change may be shown due to an error:fatal 解决方案:选择File --> s ...
- JAVA字符编码三:Java应用中的编码问题
第三篇:JAVA字符编码系列三:Java应用中的编码问题 这部分采用重用机制,引用一篇文章来完整本部分目标. 来源: Eceel东西在线 问题研究--字符集编码 地址:http://china.e ...
- C++ 中的 inline 用法
1.引入 inline 关键字的原因 在 c/c++ 中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了 inline 修饰符,表示为内联函数. 栈空间就是指放置程序的局部数 ...
- Beef搭建并通过Nginx绑定域名
Beef和Nginx安装过程这里就不再说明了相关链接:Beef官方安装教程 1. 修改Beef的config.yaml配置文件 xss连接地址改成要绑定的域名 sudo vim /beef/confi ...
- Quartus Prime 与 Modelsim 调试 及do文件使用
Quartus Prime 与 Modelsim 调试 及do文件使用 2019-06-28 11:12:50 RushBTaotao 阅读数 49更多 分类专栏: IntelFPGA-Softwar ...
- Anaconda用conda创建python虚拟环境
Anaconda用conda创建python虚拟环境 一.简介 conda可以理解为一个工具,也是一个可执行命令,其核心功能是包管理与环境管理.包管理与pip的使用类似,环境管理则允许用户方便地安装不 ...
- 支付宝小程序开发——H5跳转到小程序(获取小程序页面的链接)
前言: 这个问题支付宝小程序官方文档并没有专门说明,钉钉群的官方技术支持给了个开发者社区的帖子,详见:如何跳转小程序. 如果配置的页面没有参数还好,不会出问题,如果有参数,很可能配出来的链接无法正常获 ...