线程之全局解释器锁加一些了解知识点
---恢复内容开始---
1.GIL全局解释器锁
2.GIL与普通的互斥锁
3.死锁
4.信号量
5.event事件
6.线程q队列
7.补充 基于TCP使用线程实现高并发
一.GIL全局解释器锁
GIL jaosn总结
1 TCP服务端实现并发 2 1.将不同的功能尽量拆分成不同的函数 3 拆分出来的功能可以被多个地方使用 4 5 1.将连接循环和通信循环拆分成不同的函数 6 2.将通信循环做成多线程 7 8 9 GIL(全局解释器锁) 10 在CPython解释器才有GIL的概念,不是python的特点 11 GIL也是一把互斥锁 12 将并发变成串行 牺牲了效率但是提高了数据的安全 13 ps: 14 1.针对不同的数据 应该使用不同的锁去处理 15 2.自己不要轻易的处理锁的问题 哪怕你知道acquire和release 16 当业务逻辑稍微复杂的一点情况下 极容易造成死锁 17 CPython中的GIL的存在是因为python的内存管理不是线程安全的 18 19 内存管理 20 引用计数:值与变量的绑定关系的个数 21 标记清除:当内存快要满的时候 会自动停止程序的运行 检测所有的变量与值的绑定关系 22 给没有绑定关系的值打上标记,最后一次性清除 23 分代回收:(垃圾回收机制也是需要消耗资源的,而正常一个程序的运行内部会使用到很多变量与值 24 并且有一部分类似于常量,减少垃圾回收消耗的时间,应该对变量与值的绑定关系做一个分类 25 ) 新生代(5S)》》》青春代(10s)》》》老年代(20s) 26 垃圾回收机制扫描一定次数发现关系还在,会将该对关系移至下一代 27 随着代数的递增 扫描频率是降低的 28 29 30 同一个进程下的多个线程能否同时运行 31 GIL类似于是加在解释器上面的一把锁
View Code
什么是GIL:首先来看看官方的解释
在CPython中,这个全局解释器锁,也称之为GIL,是一个互斥锁,
防止多个线程在同一个时间执行Python字节码,这个锁是非常正要的,
因为CPython的内存管理非线程安全的,很多其他的特性依赖于GIL,
所以即使他影响了程序的效率也无法将其直接去除
总结:在CPython中,GIL会把线程的并行变成串行,导致效率降低
PS:需要知道的是,解释器并不只有CPython,还有PyPy,JPython等等。
GIL也仅存在于CPython中,这并不是python这门语言的问题,而是CPython解释器的问题
2.GIL带来的问题:
首先必须明确执行一个py文件,分为三个步骤
从硬盘加载Python解释器到内存
从硬盘加载py文件到内存
解释器解析py文件内容,交给CPU执行
其次需要明确的是每当执行一个py文件,就会立即启动一个python解释器,
当执行test.py时其内存结构如下:
GIL,叫做全局解释器锁,加到了解释器上,并且是一把互斥锁,那么这把锁对应用程序到底有什么影响?这就需要知道解释器的作用,以及解释器与应用程序代码之间的关系py文件中的内容本质都是字符串,只有在被解释器解释时,才具备语法意义,解释器会将py代码翻译为当前系统支持的指令交给系统执行。
开启子线程时,给子线程指定了一个target表示该子线程要处理的任务即要执行的代码。代码要执行则必须交由解释器,即多个线程之间就需要共享解释器,为了避免共享带来的数据竞争问题,于是就给解释器加上了互斥锁!由于互斥锁的特性,程序串行,保证数据安全,降低执行效率,GIL将使得程序整体效率降低!
3.那么为什么需要GIL锁:
GIL与GC 在使用Python中进行编程时,程序员无需参与内存的管理工作,这是因为Python有自带的内存管理机制,简称GC。那么GC与GIL有什么关联?要搞清楚这个问题,需先了解GC的工作原理,Python中内存管理使用的是引用计数,每个数会被加上一个整型的计数器,表示这个数据被引用的次数,当这个整数变为0时则表示该数据已经没有人使用,成了垃圾数据。当内存占用达到某个阈值时,GC会将其他线程挂起,然后执行垃圾清理操作,垃圾清理也是一串代码,也就需要一条线程来执行。示例代码:
from threading import Thread
def task():
a = 10
print(a)
# 开启三个子线程执行task函数
Thread(target=task).start()
Thread(target=task).start()
Thread(target=task).start()
上述代码的内存结构如下:
通过上图可以看出,GC与其他线程都在竞争解释器的执行权,而CPU何时切换,以及切换到哪个线程都是无法预支的,这样一来就造成了竞争问题,假设线程1正在定义变量a=10,而定义变量第一步会先到到内存中申请空间把10存进去,第二步将10的内存地址与变量名a进行绑定,如果在执行完第一步后,CPU切换到了GC线程,GC线程发现10的地址引用计数为0则将其当成垃圾进行了清理,等CPU再次切换到线程1时,刚刚保存的数据10已经被清理掉了,导致无法正常定义变量。
当然其他一些涉及到内存的操作同样可能产生问题问题,为了避免GC与其他线程竞争解释器带来的问题,CPython简单粗暴的给解释器加了互斥锁,如下图所示:
有了GIL后,多个线程不可能在同一时间使用解释器,从而保证了解释器的数据安全
GIL的加锁时机:在调用解释器时立即加锁
解锁时机:
当前线程遇到了IO时释放
当前线程执行时间超过设定值时释放
GIL锁有优点也有缺点:
优点:
保证了数据的安全
缺点:
互斥锁的特性使得多线程无法并行
研究python的多线程是否有用需要分情况讨论 四个任务 计算密集型的 10s 单核情况下开线程更省资源 多核情况下开进程 10s开线程 40s四个任务 IO密集型的 单核情况下开线程更节省资源 多核情况下开线程更节省资源
案例:计算密集型
1 from multiprocessing import Process 2 from threading import Thread 3 import os,time 4 def work(): 5 res=0 6 for i in range(100000000): 7 res*=i 8 9 10 if __name__ == '__main__': 11 l=[] 12 print(os.cpu_count()) # 本机为6核 13 start=time.time() 14 for i in range(6): 15 # p=Process(target=work) #耗时 4.732933044433594 16 p=Thread(target=work) #耗时 22.83087730407715 17 l.append(p) 18 p.start() 19 for p in l: 20 p.join() 21 stop=time.time() 22 print('run time is %s' %(stop-start))
View Code
案例:IO密集型
1 from multiprocessing import Process 2 3 import os,time 4 def work(): 5 time.sleep(2) 6 7 if __name__ == '__main__': 8 l=[] 9 print(os.cpu_count()) #本机为6核 10 start=time.time() 11 for i in range(4000): 12 p=Process(target=work) #耗时9.001083612442017s多,大部分时间耗费在创建进程上 13 # p=Thread(target=work) #耗时2.051966667175293s多 14 l.append(p) 15 p.start() 16 for p in l: 17 p.join() 18 stop=time.time() 19 print('run time is %s' %(stop-start))
View Code
总结:
python的多线程到底有没有用需要看情况而定 并且肯定是有用的多进程+多线程配合使用
二.GIL与普通的互斥锁区别
GIL保护的是解释器级别的数据安全,比如对象的引用计数,垃圾分代数据等等,具体参考垃圾回收机制详解。对于程序中自己定义的数据则没有任何的保护效果,这一点在没有介绍GIL前我们就已经知道了,所以当程序中出现了共享自定义的数据时就要自己加锁,如下例:
1 from threading import Thread 2 import time 3 4 n = 100 5 6 7 def task(): 8 global n 9 tmp = n 10 time.sleep(1) 11 n = tmp -1 12 13 t_list = [] 14 for i in range(100): 15 t = Thread(target=task) 16 t.start() 17 t_list.append(t) 18 19 for t in t_list: 20 t.join() 21 22 print(n)
View Code
三.死锁
死锁问题
当程序出现了不止一把锁,分别被不同的线程持有, 有一个资源 要想使用必须同时具备两把锁
这时候程序就会进程无限卡死状态 ,这就称之为死锁
案例:
案例:
from threading import Thread RLock import timemutexA = Lock() mutexB = Lock()class MyThread(Thread):def run(self): # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发 self.func1()self.func2()def func1(self):mutexA.acquire()print('%s抢到了A锁'%self.name) # self.name等价于current_thread().name mutexB.acquire()print('%s抢到了B锁'%self.name)mutexB.release()print('%s释放了B锁'%self.name)mutexA.release()print('%s释放了A锁'%self.name)def func2(self):mutexB.acquire()print('%s抢到了B锁'%self.name)time.sleep(1)mutexA.acquire()print('%s抢到了A锁' % self.name)mutexA.release()print('%s释放了A锁' % self.name)mutexB.release()print('%s释放了B锁' % self.name)for i in range(10):t = MyThread()t.start()
这样就会产生死锁现象:解释 首先执行的是func1然后线程1抢到A锁,其他九个线程需要的需要等待A锁被释放,线程1不会释放A锁,紧接着就会抢B锁,B锁是没有人抢的,所以直接就可以拿到B锁,然后释放B锁,紧接着释放A锁,那么其他九个在等待的线程看到A锁被线程1释放了,他们就会立马区抢,那么线程1已经区执行func2了,因为其他九个线程都在func1哪里抢,线程1直接就可以抢到B锁,然后线程1会进入阻塞状态1秒,但是线程1还持有者B锁,func1里面的线程也开始抢B锁了,1秒阻塞状态过去,线程1也需要抢A锁了,但是A锁被func1里面的线程持有,这样就会产生我要你的A锁,你要我的B锁,但是都给不了,就会卡住
补充知识点:
Rlock 称之为递归锁或者可重入锁
与Lock唯一的区别: Rlock同一线程可以多次执行acquire 但是执行几次acquire就应该对应release几次 如果一个线程已经执行过acquire 其他线程将无法执行acquire
案例:
1 from threading import Thread,Lock,current_thread,RLock 2 import time 3 """ 4 Rlock可以被第一个抢到锁的人连续的acquire和release 5 每acquire一次锁身上的计数加1 6 每release一次锁身上的计数减1 7 只要锁的计数不为0 其他人都不能抢 8 9 """ 10 # mutexA = Lock() 11 # mutexB = Lock() 12 mutexA = mutexB = RLock() # A B现在是同一把锁 13 14 15 class MyThread(Thread): 16 def run(self): # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发 17 self.func1() 18 self.func2() 19 20 def func1(self): 21 mutexA.acquire() 22 print('%s抢到了A锁'%self.name) # self.name等价于current_thread().name 23 mutexB.acquire() 24 print('%s抢到了B锁'%self.name) 25 mutexB.release() 26 print('%s释放了B锁'%self.name) 27 mutexA.release() 28 print('%s释放了A锁'%self.name) 29 30 def func2(self): 31 mutexB.acquire() 32 print('%s抢到了B锁'%self.name) 33 time.sleep(1) 34 mutexA.acquire() 35 print('%s抢到了A锁' % self.name) 36 mutexA.release() 37 print('%s释放了A锁' % self.name) 38 mutexB.release() 39 print('%s释放了B锁' % self.name) 40 41 for i in range(10): 42 t = MyThread() 43 t.start()
View Code
四.信号量
可以现在被锁定的代码 同时可以被多少线程并发访问
互斥锁: 锁住一个马桶 同时只能有一个
信号量: 锁住一个公共厕所 同时可以来一堆人
用途: 仅用于控制并发访问 并不能防止并发修改造成的问题
案例:
1 from threading import Semaphore,Thread 2 import time 3 import random 4 5 sm = Semaphore(5) # 造了一个含有五个的坑位的公共厕所 6 7 def task(name): 8 sm.acquire() 9 print('%s占了一个坑位'%name) 10 time.sleep(random.randint(1,3)) 11 sm.release() 12 13 for i in range(40): 14 t = Thread(target=task,args=(i,)) 15 t.start()
View Code
五.event事件
什么是事件
事件表示在某个时间发生了某个事情的通知信号,用于线程间协同工作。
因为不同线程之间是独立运行的状态不可预测,所以一个线程与另一个线程间的数据是不同步的,当一个线程需要利用另一个线程的状态来确定自己的下一步操作时,就必须保持线程间数据的同步,Event就可以实现线程间同步
可用的一些方法:
event.isSet():返回event的状态值; event.wait():将阻塞线程;知道event的状态为True event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度; event.clear():恢复event的状态值为False
案例:
1 from threading import Event,Thread 2 import time 3 4 # 先生成一个event对象 5 e = Event() 6 7 8 def light(): 9 print('红灯正亮着') 10 time.sleep(3) 11 e.set() # 发信号 12 print('绿灯亮了') 13 14 def car(name): 15 print('%s正在等红灯'%name) 16 e.wait() # 等待信号 17 print('%s加油门飙车了'%name) 18 19 t = Thread(target=light) 20 t.start() 21 22 for i in range(10): 23 t = Thread(target=car,args=('伞兵%s'%i,)) 24 t.start()
View Code
六.线程q对列
同一个进程下的多个线程本来就是数据共享 为什么还要用队列因为队列是管道+锁 使用队列你就不需要自己手动操作锁的问题 因为锁操作的不好极容易产生死锁现象
1.Queue 先进先出队列
与多进程中的Queue使用方式完全相同,区别仅仅是不能被多进程共享。
案例:
q = Queue(3) q.put(1) q.put(2) q.put(3) print(q.get(timeout=1)) print(q.get(timeout=1)) print(q.get(timeout=1))
2.LifoQueue 后进先出队列
该队列可以模拟堆栈,实现先进后出,后进先出
案例:
lq = LifoQueue()lq.put(1) lq.put(2) lq.put(3)print(lq.get()) print(lq.get()) print(lq.get())
3.PriorityQueue 优先级队列
该队列可以为每个元素指定一个优先级,这个优先级可以是数字,字符串或其他类型,但是必须是可以比较大小的类型,取出数据时会按照从小到大的顺序取出
案例:
pq = PriorityQueue() # 数字优先级 pq.put((10,"a")) pq.put((11,"a")) pq.put((-11111,"a"))print(pq.get()) print(pq.get()) print(pq.get()) # 字符串优先级 pq.put(("b","a")) pq.put(("c","a")) pq.put(("a","a"))print(pq.get()) print(pq.get()) print(pq.get())
补充:如何解决基于TCP的高并发
server:
1 import socket 2 from threading import Thread 3 4 """ 5 服务端 6 1.要有固定的IP和PORT 7 2.24小时不间断提供服务 8 3.能够支持并发 9 """ 10 11 server = socket.socket() 12 server.bind(('127.0.0.1',8080)) 13 server.listen(5) 14 15 16 def talk(conn): 17 while True: 18 try: 19 data = conn.recv(1024) 20 if len(data) == 0:break 21 print(data.decode('utf-8')) 22 conn.send(data.upper()) 23 except ConnectionResetError as e: 24 print(e) 25 break 26 conn.close() 27 28 while True: 29 conn, addr = server.accept() # 监听 等待客户端的连接 阻塞态 30 print(addr) 31 t = Thread(target=talk,args=(conn,)) 32 t.start()
View Code
client:
1 import socket 2 3 4 client = socket.socket() 5 client.connect(('127.0.0.1',8080)) 6 7 while True: 8 client.send(b'hello') 9 data = client.recv(1024) 10 print(data.decode('utf-8'))
View Code
转载于:https://www.cnblogs.com/zahngyu/p/11353069.html
线程之全局解释器锁加一些了解知识点相关推荐
- 线程与全局解释器锁(GIL)
一.线程概论 1.何为线程 每个进程有一个地址空间,而且默认就有一个控制线程.如果把一个进程比喻为一个车间的工作过程那么线程就是车间里的一个一个流水线. 进程只是用来把资源集中到一起(进程只是一个资源 ...
- Python进阶并发基础--线程,全局解释器锁GIL由来,如何更好的利用Python线程,
全局解释器锁GIL 官方对于线程的介绍: 在 CPython 中,由于存在全局解释器锁,同一时刻只有一个线程可以执行 Python代码(虽然某些性能导向的库可能会去除此限制).如果你想让你的应用更好地 ...
- c语言的锁和Python锁,Python中全局解释器锁、多线程和多进程
全局解释器锁(GIL)只允许1个Python线程控制Python解释器.这也就意味着同一时间点只能有1个Python线程运行.如果你的Python程序只有一个线程,那么全局解释器锁可能对你的影响不大, ...
- python开发线程:线程守护线程全局解释器锁
From: https://www.cnblogs.com/jokerbj/p/7460260.html 一 threading模块介绍 multiprocess模块的完全模仿了threading模块 ...
- python-生产者消费者模型_线程_线程互斥锁_GIL全局解释器锁
进程 1. 开启进程的两种方式 2. 进程对象其他属性和方法-pid: 进程id号 os.getpid()-ppid: 父进程id号 os.getppid() -is_alive(): 当前进程是否存 ...
- 【Python爬虫学习笔记11】Queue线程安全队列和GIL全局解释器锁
Queue线程安全队列 在Python多线程编程中,虽然threading模块为我们提供了Lock类和Condition类借助锁机制来处理线程并发执行,但在实际开发中使用加锁和释放锁仍是一个经常性的且 ...
- gil php,网络编程之多线程——GIL全局解释器锁
网络编程之多线程--GIL全局解释器锁 一.引子 定义: In CPython, the global interpreter lock, or GIL, is a mutex that preven ...
- Python中的GIL(全局解释器锁)
1. GIL全称Global Interpreter Lock,每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程可以执行代码. 2.GIL的缺点 GIL使Python不能充分利用多核心 ...
- python基础--GIL全局解释器锁、Event事件、信号量、死锁、递归锁
ps:python解释器有很多种,最常见的就是C python解释器 GIL全局解释器锁: GIL本质上是一把互斥锁:将并发变成串行,牺牲效率保证了数据的安全 用来阻止同一个进程下的多个线程的同时执行 ...
最新文章
- 基于深度学习的目标检测研究进展
- 批量恢复加密图像,联邦学习真的危了? | CVPR 2021
- Linux 用户管理相关命令
- 关闭 启动_Steam如何关闭开机自动启动
- maven的java工程取mysql数据库数据
- Apache Zookeeper入门1
- sop4封装尺寸图_「光电封装」 有源光器件的结构和封装
- python自定义assert抛出的异常
- Bootstrap+Font Awesome图标不显示 或显示错误解决办法
- 【转】状态压缩动态规划
- VSCode设置中文语言
- oracle分析函数sum() over()
- 思维导图 XMind 闯关之路(第02关)插入各类符号
- Google Analytics中的基本度量四 “页面停留时间和网站停留时间
- html实现手机截屏,iPhone手机如何实现网页长截图?
- jira是干什么_如何用JIRA来做需求管理?
- 【高效程序员系列】3 别碰鼠标——让键盘飞起来
- linux上安装Weblogic11g 详解
- 实用干货!正规的问答推广平台有哪些及其优势
- VL813-Q7威锋一出四HUB芯片方案
热门文章
- Ubuntu 16.04 中 安装 Docker
- flask中蓝图的使用
- Python3高级 之 协程
- 解释一下全连接层CNN中全连接层是什么样的
- ashx文件的几种使用
- beta分布的采样或抽样(java程序)
- 解决透视变换后图片信息丢失的问题
- linux ftp 登录慢,linux中vsftpd登录,上传下载文件慢解决办法linux操作系统 -电脑资料...
- mysql怎么退出时保存导出_Mysql应用使用MySQL MySqldump命令导出数据时的注意事项...
- 设计模式学习笔记——状态(State)模式框架