Python-全局解释器锁GIL原理和多线程产生原因与原理-多线程通信机制
GIL
全局解释器锁,这个锁是个粗粒度的锁,解释器层面上的锁,为了保证线程安全,同一时刻只允许一个线程执行,但这个锁并不能保存线程安全,因为GIL会释放掉的并且切换到另外一个线程上,不会完全占用,依据分配策略(时间片、执行字节码行数、IO操作)。GIL只能保证同一时刻同一CPU上只有一个线程执行,但不能保证线程切换的时候能把一行代码翻译成的bytecode执行完,这就会出现问题,所以说只是一定程度上的保证线程安全。GIL 使得同一个时刻只有一个线程在一个CPU上执行字节码,无法将多个线程映射到多个CPU上,这也是为什么这么说Python多线程鸡肋,也就是说根本意义上来说Python是个单线程的,这个GIL只在CPython中才有,其他版本中没有。
但单线程有个非常大的优势就是在处理IO密集型的任务时候显得与众不同,Python中分线程和协程两种方式,线程是由系统控制调用,协程是由程序员控制,也就是说在把控度角度上来看协程比线程更加具有优势。其一协程比线程更加轻量,其二协程切换完全由程序员控制而不是操作系统控制,其三协程切换消耗远比线程小。
北门吹雪:http://www.cnblogs.com/2bjiujiu/
Python 代码执行原理
# Python代码 --> 解释器 --> bytecode --> 一行一行执行 bytecode

函数执行过程原理:
import dis def add(a):
a += 1 def subtract(a):
a -= 1 if __name__ == '__main__':
# add 函数执行过程
dis.dis(add)
print()
# subtract 函数执行过程
dis.dis(subtract)
结果:
5 0 LOAD_FAST 0 (a)
2 LOAD_CONST 1 (1)
4 INPLACE_ADD
6 STORE_FAST 0 (a)
8 LOAD_CONST 0 (None)
10 RETURN_VALUE 9 0 LOAD_FAST 0 (a)
2 LOAD_CONST 1 (1)
4 INPLACE_SUBTRACT
6 STORE_FAST 0 (a)
8 LOAD_CONST 0 (None)
10 RETURN_VALUE
简化一下执行结果过程:
def add(a):
a += 1
add
1. load a
2. load 1
3. +
4. 赋值给a a = a +1
def desc(a):
a -= 1
desc
1. load a
2. load 1
3. -
4. 赋值给a a = a - 1
以上过程 GIL会在其中某个过程释放掉给其他线程,问题出现在4赋值阶段,多个线程切换,最终a的值处于各自线程最终结果,假如a=1,则add线程赋值 a = 2 然而 desc线程赋值 a = 0,那么问题出现了,为了解决这个问题,引入锁的概念,线程同步机制,锁住出现问题的代码段,锁住代码块之间串行。获得锁和释放锁带来性能问题,并且带来死锁问题。
beimenchuixue:http://www.cnblogs.com/2bjiujiu/
锁
1. 普通锁 threading.Lock
2. 重入锁(递归锁) threading.Rlodk
# 重入锁使用条件,必须在同一线程内,连续调用多次acquire,一定要注意acquire和 release的次数相等
# 重入锁一般用于一个线程中嵌套一层逻辑,里层逻辑使用了锁机制
如何产生死锁:
1. 获取锁并没有释放锁同时又获取锁
2. 线程A锁住线程B希望获得锁的资源没有释放,线程B锁住线程A的希望获取的资源并没有是否掉,也就是说两个线程各自锁住对方想要获取锁的资源没有释放掉。
3. 一个线程函数中,获得锁之后,再次调用另外一个函数,另外一个函数也获得锁, 通过 Rlodk锁解决这个问题
# 死锁本质原因用土话说:吃着碗里的看着锅里的或互相锁住对象想要的
线程:
1. 操作系统调度单的最小单位,对于IO操作多进程和多线程差别不大
2. 线程出现的原因是进程粒度太大,希望更小的单元使用CPU资源,CPU上执行的是线程,CPU执行线程代码或指令集
3. 线程是进程的一部分,进程至少有一个主线程
4. 线程本身不能分配和拥有资源,只能访问和使用进程资源,因此切换线程比进程切换更加快
5. 多线程可以利用多核和多个CPU性能资源优势,但在Python中不行,由于GIL锁的存在
6. 多线程也是异步编程方式,大多数情况下主线程不知道子线程是否执行往,主线程也不依赖子线程的执行结果
北门吹雪:http://www.cnblogs.com/2bjiujiu/
CPU密集型
1. 处理数据(计算)
IO密集型
# 等待数据
1. 查询数据库
2. 请求网络资源
3. 读写文件
# 依据程序花费的时间是在CPU上还是在等待数据上判断这个程序是CPU密集型还是IO密集型
IO操作主要分两类: 网络IO 和 磁盘IO

多线程模块 threading
threading使用过程
1. 实例线程
2. 启动线程
3. 等待或者不等待子线程执行
这里使用socket server线程实现方式实例:
import socket
from threading import Thread class BeiMenChuiXueHandle(Thread):
def __init__(self, conn, remote_address):
super().__init__()
self.conn = conn
self.remote_address = remote_address def run(self):
while True:
try:
data = self.conn.recv(1024).decode("utf-8")
except ConnectionResetError as e:
print("北门吹雪")
print("%s:%s 断开连接" % self.remote_address)
break
self.conn.send(bytes(data.upper(), encoding='utf-8'))
self.conn.close() class BeiMenChuiXueHandleSocket:
def __init__(self):
self.listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) def _handle(self, conn, remote_address):
# 1实例线程
handler = BeiMenChuiXueHandle(conn, remote_address)
# 启动线程
handler.start() def socket_run(self, ip, port):
self.listener.bind((ip, port))
self.listener.listen()
while True:
conn, remote_address = self.listener.accept()
print("北门吹雪")
print("%s:%s 建立连接" % remote_address)
# 3. 线程去处理连接,因为服务端是一直运行,也就是说是否等待进程完成已经不需要
self._handle(conn, remote_address) if __name__ == '__main__':
listener = BeiMenChuiXueHandleSocket()
listener.socket_run(ip='0.0.0.0', port=8000)
对线程相关操作,这些操作都是在线程实例上的方法
1. 设置为守护线程 .setDaemon(True)
# 主线程不等待子线程完成,其实不加上jion都是守护线程,加上语义更加明显而已
2. 堵塞等待线程完成 .join
# 这个是写在主线程中,在这一行如果线程没有执行完则产生堵塞等待线程执行完,线程执行完则不堵塞执行后面的代码
3. 设置线程名字 .setname
# 其实在实例化的时候可以设置线程名字,有一个name参数
4. 获取线程名字 .getname
import socket
from threading import Thread def beimenchuixue_handler(conn, remote_address):
"""线程处理处理每个连接"""
# 处理客户端端口异常
while True:
try:
data = conn.recv(1024).decode("utf-8")
except ConnectionResetError as e:
print("%s:%s 断开连接" % remote_address)
break
conn.send(bytes(data.upper(), encoding='utf-8'))
conn.close() def beimenchuixue_socket():
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listener.bind(('0.0.0.0', 8000))
listener.listen()
while True:
conn, remote_address = listener.accept()
print("%s:%s 建立连接" % remote_address)
# 通过name设置线程名
handler = Thread(target=beimenchuixue_handler, args=(conn, remote_address), name="北门吹雪")
# 设置为守护线程,必须在启动前设置
handler.setDaemon(True)
handler.start()
# 获得线程名
print(handler.getName())
# 线程是否激活
print(handler.is_alive())
# 判断是否守护线程
print(handler.isDaemon())
# 和 is_active()一样
print(handler.isAlive()) if __name__ == '__main__':
beimenchuixue_socket()
5. 获得进程中主线程编号 threading.currennt_thread()
# 这个可以放到任何地方,结果是一样的
北_门_吹_雪:http://www.cnblogs.com/2bjiujiu/
线程通信
1. 共享变量
在线程函数上一层作用域定义一个变量,这线程函数都能方法到并能做修改,或者通过把公用变量传递到线程函数中)线程安全性(相对操作的变量来说,需要加锁让线程依次操作变量),虽然有时没有锁的情况下结果依然正确,因为GIL这把大锁的存在
from threading import Thread, Lock
from itertools import chain # 共享变量,本质上是上一级变量,要修改则通过global和 nonlocal申明不是当前局变量
number = 1
lock = Lock() def add():
global number
# 获取锁
lock.acquire()
number += 1
# 释放锁
lock.release() def subtract():
global number
lock.acquire()
number -= 1
lock.release() if __name__ == '__main__': go_add_threads = {Thread(target=add, name='add_%s' % i) for i in range(100)}
go_subtract_threads = {Thread(target=subtract, name="add_thread_%s" % i) for i in range(100)}
# 启动并等待线程
for add_thread in chain(go_add_threads, go_subtract_threads):
add_thread.start()
for add_thread in chain(go_add_threads, go_subtract_threads):
add_thread.join()
# 获得共享变量值
print(number)
2. 通过Queue通信,Queue是线程安全,底层是使用deque数据结构实现,类似Go语言中CSP线程之间通信
# 下面这个实例模仿包子生产店子实现一个生产者与消费者模型实例,通过随机模块模拟等待顾客的过程,通过缓存队列模拟生产包子满了处于等待状态
from threading import Thread
import time
from queue import Queue
from random import randint def producer(baozi_queue):
count = 1
while True:
print("-->包子出笼了,第%s包子" % count)
baozi_queue.put(count)
# 模拟做包子过程
time.sleep(1)
count += 1 def consumer(baozi_queue):
while True:
count = baozi_queue.get()
# 模拟吃包子过程,不是每时每刻都会有顾客,模拟等待顾客过程
time.sleep(randint(1, 10))
print("<--吃完第 %s 包子" % count) if __name__ == '__main__':
# 缓存的队列, 做完10个包子就不再做了
baozi_queue = Queue(maxsize=10)
# 生产者
baozi_worker = Thread(target=producer, args=(baozi_queue, ), name="北门吹雪包子店,百年品牌老店")
# 消费者
baozi_consumer = Thread(target=consumer, args=(baozi_queue,), name="吃包子")
# 启动这两个线程
baozi_worker.start()
baozi_consumer.start()
baozi_worker.join()
baozi_consumer.join()
Queue队列其他相关方法和参数
get 有两个参数,block是否堵塞,timeout堵塞超时时间,其中一个条件触发则抛出异常
empty 判断队列是否为空
full 判断队列是否满
北_门_吹_雪:http://www.cnblogs.com/2bjiujiu/
经验:
1. Python多线程由于GIL存在本质上只是单线程,无法利用多核CPU性能优势,启动多进程某种意义上来说只是多启动了一个线程,启动新进程本身消耗远比线程资源消耗大多了,启动进程来折中解决Python线程问题不是好办法
2. 死锁本质原因是吃着碗里的看着锅里的或者互相锁住对方想要的资源
3. 采用Go语言中CSP线程通信模式解决线程安全问题,我觉得是当下最优的选择
4. 区分程序是CPU密集型还是IO密集型,主要看程序把时间花费在CPU上还是在等待数据上
5. 事件驱动,本质上是发送请求取数据,在等待取数据的时候去执行其他函数或过程,跳出时候注册一个事件,数据取回触发事件重新回到跳出的部分执行往下的代码,架构是:事件循环+回调函数
6. 事件驱动异步编程,虽然性能很优秀,但打破了传统上从下到下顺序执行的逻辑,把代码执行的逻辑彻底打乱,出错排错都是非常棘手问题,到底是选择异步编程还是同步编程,需要充分考虑技术人员技术能力和业务需求,如果不能满足则换成Go语言等本身支持异步编程的高性能语言进行处理业务需求,而不是死磕Python实现,这样死磕Python则会在维护与开发成本上花费更多也可能达不到预期效果
bei_men_chui_xxue:http://www.cnblogs.com/2bjiujiu/
Python-全局解释器锁GIL原理和多线程产生原因与原理-多线程通信机制的更多相关文章
- Python核心技术与实战——十九|一起看看Python全局解释器锁GIL
我们在前面的几节课里讲了Python的并发编程的特性,也了解了多线程编程.事实上,Python的多线程有一个非常重要的话题——GIL(Global Interpreter Lock).我们今天就来讲一 ...
- Python全局解释器锁 -- GIL
首先强调背景: 1.GIL是什么?GIL的全称是Global Interpreter Lock(全局解释器锁),来源是python设计之初的考虑,为了数据安全所做的决定. 2.每个CPU在同一时间只能 ...
- python 线程队列、线程池、全局解释器锁GIL
一.线程队列 队列特性:取一个值少一个,只能取一次,没有值的时候会阻塞,队列满了,也会阻塞 queue队列 :使用import queue,用法与进程Queue一样 queue is especial ...
- python 什么是全局解释器锁GIL
什么是全局解释器锁GIL Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在 ...
- 全局解释器锁GIL
我们使用高并发,一次是创建1万个线程去修改一个数并打印结果看现象: from threading import Thread import os def func(args): global n n ...
- python 全局解释锁GIL
Python的全局解释器锁GIL用于保护python解释器,使得任意时刻,只有一个线程在解释器中运行.从而保证线程安全 在多线程环境中,Python 虚拟机按以下方式执行: 1. 设置GIL2. 切换 ...
- 并发编程——全局解释器锁GIL
1.全局解释器锁GIL GIL其实就是一把互斥锁(牺牲了效率但是保证了数据的安全). 线程是执行单位,但是不能直接运行,需要先拿到python解释器解释之后才能被cpu执行 同一时刻同一个进程内多个线 ...
- 21.线程,全局解释器锁(GIL)
import time from threading import Thread from multiprocessing import Process #计数的方式消耗系统资源 def two_hu ...
- 全局解释器锁GIL & 线程锁
1.GIL锁(Global Interpreter Lock) Python代码的执行由Python虚拟机(也叫解释器主循环)来控制.Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行 ...
- python 多线程编程之使用进程和全局解释器锁GIL
本文主要介绍如何在python中使用线程. 全局解释器锁: python代码的执行是由python虚拟机(又名解释器主循环)进行控制的.python中,主循环中同时只能有一个控制线程在执行,就像单核C ...
随机推荐
- jQuery源码分析系列(一)初识jQuery
一个工厂 (function(global, factory){ "use strict" // operation_1 })(typedef window !== "u ...
- [CSP-S2019]划分 题解
CSP-S2 2019 D2T2 考场上读完题感觉是DP就直接扔了开T3了,考完才发现部分分好像不难拿,枯了 题意分析 给出一个数列,要求将其分成几段,使每段的和非严格递增,求最小的每段的和的平方和. ...
- session 机制和 httpsession 详解 (转载)
https://www.cnblogs.com/bjanzhuo/archive/2013/02/27/3575884.html 一.术语session 在我的经验里,session这个词被滥用的程度 ...
- 网站会不会因为同IP网站被K而受到惩罚
http://www.wocaoseo.com/thread-289-1-1.html 使用虚拟主机的网站会不会因为同一个IP或同一台服务器上有其他网站作弊被处罚而受连累,也被惩罚? 复制代码 百度官 ...
- 力扣Leetcode 面试题51. 数组中的逆序对 - 归并排序
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数. 示例 1: 输入: [7,5,6,4] 输出: 5 限制: 0 <= ...
- EXCEL 引用autocad vba,自动加载其类型库
Sub AutoADDAutoCADTypeLib() Dim Ref As Variant Dim hasAutoTypeLib As Boolean, hasAXDBLib As Boolean, ...
- [BUUOJ记录] [HCTF 2018]WarmUp
BUUOJ Web的第一题,其实是很有质量的一道题,但是不知道为什么成了Solved最多的题目,也被师傅们笑称是“劝退题”,这道题的原型应该是来自于phpMyadmin的一个文件包含漏洞(CVE-20 ...
- JavaScript 的 this 指向和绑定详解
JavaScript 中的 new.bind.call.apply 实际这些都离不开 this,因此本文将着重讨论 this,在此过程中分别讲解其他相关知识点. 注意: 本文属于基础篇,请大神绕路.如 ...
- GitHub 热点速览 Vol.36:当股票遇到机器学习,异常股无所遁形
作者:HelloGitHub-小鱼干 摘要:虽然上周的 GitHub Trending 榜新项目寥寥无几,但胜在表现不俗,例如:通过机器学习来检测异常股票的项目 Surpriver,还有腾讯开源的管理 ...
- 15个随机图片API
15个随机图片API 妈妈再也不用担心我网站没图用了呜 请不要重复刷新此页面 ! 找了很久的说,你难道不想收藏一下吗 其中有些 API 速度并不太好,可能会拖慢贵站的速度 我也不能保证这些 API 能 ...