GIL锁

计算机有4核,代表着同一时间,可以干4个任务。如果单核cpu的话,我启动10个线程,我看上去也是并发的,因为是执行了上下文的切换,让看上去是并发的。但是单核永远肯定时串行的,它肯定是串行的,cpu真正执行的时候,因为一会执行1,一会执行2.。。。。正常的线程就是这个样子的。但是,在python中,无论有多少核,永远都是假象。无论是4核,8核,还是16核.......不好意思,同一时间执行的线程只有一个(线程),它就是这个样子的。这个是python的一个开发时候,设计的一个缺陷,所以说python中的线程是假线程。

1、全局解释器锁(GIL)

 无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行

2、GIL存在的意义?

 因为python的线程是调用操作系统的原生线程,这个原生线程就是C语言写的原生线程。因为python是用C写的,启动的时候就是调用的C语言的接口。因为启动的C语言的远程线程,那它要调这个线程去执行任务就必须知道上下文,所以python要去调C语言的接口的线程,必须要把这个上限问关系传给python,那就变成了一个我在加减的时候要让程序串行才能一次计算。就是先让线程1,再让线程2.......

 每个线程在执行的过程中,python解释器是控制不了的,因为是调的C语言的接口,超出了python的控制范围,python的控制范围是只在python解释器这一层,所以python控制不了C接口,它只能等结果。所以它不能控制让哪个线程先执行,因为是一块调用的,只要一执行,就是等结果,这个时候4个线程独自执行,所以结果就不一定正确了。有了GIL,就可以在同一时间只有一个线程能够工作。虽然这4个线程都启动了,但是同一时间我只能让一个线程拿到这个数据。其他的几个都干等。python启动的4个线程确确实实落到了这4个cpu上,但是为了避免出错。这也是Cpython的一个缺陷,其他语言没有,仅仅只是Cpython有。

3、GIL锁关系图

GIL(全局解释器锁)是加在python解释器里面的,效果如图:

如上图,为什么GIL锁要加在python解释器这一层,而却不加在其他地方?

因为你python调用的所有线程都是原生线程。原生线程是通过C语言提供原生接口,相当于C语言的一个函数。你一调它,你就控制不了了它了,就必须等它给你返回结果。只要已通过python虚拟机,再往下就不受python控制了,就是C语言自己控制了。你加在python虚拟机以下,你是加不上去的。同一时间,只有一个线程穿过这个锁去真正执行。其他的线程,只能在python虚拟机这边等待。

 总结:

需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL。

线程锁(互斥锁)

线程需要沟通,需要共享数据,但是我们之前并没有涉及到多线程情况共享数据的例子。下面就来看看,多线程共享数据会出现什么情况

 1、多进程共享数据(python2.7中实验)

import threading
import time def run(n):
global num # 把num变成全局变量
time.sleep(1) # 注意了sleep的时候是不占有cpu的,这个时候cpu直接把这个线程挂起了,此时cpu去干别的事情去了
num += 1 # 所有的线程都做+1操作 num = 0 # 初始化num为0
t_obj = list()
for i in range(100):
t = threading.Thread(target=run, args=("t-{0}".format(i),))
t.start()
t_obj.append(t) for t in t_obj:
t.join() print("--------all thread has finished")
print("num:", num) # 输出最后的num值 #执行结果
--------all thead has finished
('num:', 97) #输出的结果

最后输出的结果怎么会是 97 呢?应该是100才对啊,不是有GIL(全局解释器锁)已经控制了,为什么最后的输出结果还是错误?

其实这种情况只能在python2.x 中才会出现的,python3.x里面没有这种现象,下面我们就用一张图来解释一下这个原因。如图:

解释:

  1. 到第5步的时候,可能这个时候python正好切换了一次GIL(据说python2.7中,每100条指令会切换一次GIL),执行的时间到了,被要求释放GIL,这个时候thead 1的count=0并没有得到执行,而是挂起状态,count=0这个上下文关系被存到寄存器中.
  2. 然后到第6步,这个时候thead 2开始执行,然后就变成了count = 1,返回给count,这个时候count=1.
  3. 然后再回到thead 1,这个时候由于上下文关系,thead 1拿到的寄存器中的count = 0,经过计算,得到count = 1,经过第13步的操作就覆盖了原来的count = 1的值,所以这个时候count依然是count = 1,所以这个数据并没有保护起来。

2、添加线程锁

通过上面的图我们知道,结果依然是不准确的。所以我还要加一把锁,这个是用户级别的锁。

import threading
import time def run(n):
lock.acquire() # 添加线程锁
global num # 把num变成全局变量
time.sleep(0.1) # 注意了sleep的时候是不占有cpu的,这个时候cpu直接把这个线程挂起了,此时cpu去干别的事情去了
num += 1 # 所有的线程都做+1操作
lock.release() # 释放线程锁 num = 0 # 初始化num为0
lock = threading.Lock() # 生成线程锁实例
t_obj = list()
for i in range(10):
t = threading.Thread(target=run, args=("t-{0}".format(i),))
t.start()
t_obj.append(t) for t in t_obj:
t.join() # 为join是等子线程执行的结果,如果不加,主线程执行完,下面就获取不到子线程num的值了,共享数据num值就错误了 print("--------all thread has finished")
print("num:", num) # 输出最后的num值

 小结:

  1. 用theading.Lock()创建一个lock的实例。
  2. 在线程启动之前通过lock.acquire()加加锁,在线程结束之后通过lock.release()释放锁。
  3. 这层锁是用户开的锁,就是我们用户程序的锁。跟我们这个GIL没有关系,但是它把这个数据相当于copy了两份,所以在这里加锁,以确保同一时间只有一个线程,真真正正的修改这个数据,所以这里的锁跟GIL没有关系,你理解就是自己的锁。
  4. 加锁,说明此时我来去修改这个数据,其他人都不能动。然后修改完了,要把这把锁释放。这样的话就把程序编程串行了。

3、使用场景

 在用户层面加锁,使程序变成串行了,那我们在什么情况下用呢?

  1、我们在程序中间不能有sleep,因为程序变成串行,这样你再sleep,程序执行的时间就会变长。

  2、我们使用的时候确保数据量不是特别大,如果数据量大,也会影响我们的执行效率。

  3、如果你程序结束时,不释放锁的话,而且程序又是串行的,则就是占着坑,那永远在那边等着,所以最后需要释放锁。

递归锁(RLock)

1、线程死锁

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都

正在使用,所有这两个线程在无外力作用下将一直等待下去

import threading

def run1():
print("grab the first part data")
lock.acquire() # 修改num前加锁
global num
num += 1
lock.release() # 释放锁
return num def run2():
print("grab the second part data")
lock.acquire() # 修改num2前加锁
global num2
num2 += 1
lock.release() # 释放锁
return num2 def run3():
lock.acquire() # 加锁
res = run1() # 执行run1函数
print('--------between run1 and run2-----')
res2 = run2() # 执行run2函数
lock.release() # 释放锁
print(res, res2) if __name__ == '__main__':
num, num2 = 0, 0
lock = threading.Lock() # 设置锁的全局变量
for i in range(10):
t = threading.Thread(target=run3)
t.start() while threading.active_count() != 1: # 判断是否只剩主线程了
print(threading.active_count())
else:
print('----all threads done---')
print(num, num2)

上面的执行结果,是无限的进入死循环,所以不能这么加,这个时候就需要用到递归锁。

2、递归锁(RLock)

import threading

def run1():
print("grab the first part data")
lock.acquire() # 修改num前加锁
global num
num += 1
lock.release() # 释放锁
return num def run2():
print("grab the second part data")
lock.acquire() # 修改num2前加锁
global num2
num2 += 1
lock.release() # 释放锁
return num2 def run3():
lock.acquire() # 加锁
res = run1() # 执行run1函数
print('--------between run1 and run2-----')
res2 = run2() # 执行run2函数
lock.release() # 释放锁
print(res, res2) if __name__ == '__main__':
num, num2 = 0, 0
lock = threading.RLock() # 只用修改这里,把线程锁lock()更改成递归锁RLock()的全局变量
for i in range(10):
t = threading.Thread(target=run3)
t.start() while threading.active_count() != 1: # 判断是否只剩主线程了
print(threading.active_count())
else:
print('----all threads done---')
print(num, num2)

递归锁原理其实很简单:就是每开一把门,在字典里面存一份数据,退出的时候去到door1或者door2里面找到这个钥匙退出,如图:

lock = {
door1:key1,
door2:key2
}

注:递归锁用于多重锁的情况,如果只是一层锁,就用不上递归锁,在实际情况下,递归锁场景用的不是特别多。

【python】-- GIL锁、线程锁(互斥锁)、递归锁(RLock)的更多相关文章

  1. Python并发编程05 /死锁现象、递归锁、信号量、GIL锁、计算密集型/IO密集型效率验证、进程池/线程池

    Python并发编程05 /死锁现象.递归锁.信号量.GIL锁.计算密集型/IO密集型效率验证.进程池/线程池 目录 Python并发编程05 /死锁现象.递归锁.信号量.GIL锁.计算密集型/IO密 ...

  2. Python GIL、线程锁、信号量及事件

    GIL是什么? GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念.就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码.有名的编 ...

  3. python 之 并发编程(守护线程与守护进程的区别、线程互斥锁、死锁现象与递归锁、信号量、GIL全局解释器锁)

    9.94 守护线程与守护进程的区别 1.对主进程来说,运行完毕指的是主进程代码运行完毕2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕​详细解释:1.主 ...

  4. 并发编程(五)——GIL全局解释器锁、死锁现象与递归锁、信号量、Event事件、线程queue

    GIL.死锁现象与递归锁.信号量.Event事件.线程queue 一.GIL全局解释器锁 1.什么是全局解释器锁 GIL本质就是一把互斥锁,相当于执行权限,每个进程内都会存在一把GIL,同一进程内的多 ...

  5. JoinableQueue队列,线程,线程于进程的关系,使用线程,线程的特点,守护线程,线程的互斥锁,死锁问题,递归锁,信号量

    1.JoinableQueue队列 JoinableQueue([maxsize]):这就像是一个Queue对象,但是队列允许项目的使用者通知生成者项目已经被成功处理.通知进程是使用共享的信号和条件变 ...

  6. Python之网路编程之死锁,递归锁,信号量,Event事件,线程Queue

    一.死锁现象与递归锁 进程也是有死锁的 所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用, 它们都将无法推进下去.此时称系统处于死锁状态或系统 ...

  7. 线程之死锁、递归锁、信号量、事件Event 、定时器

    1.死锁的现象 所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相 ...

  8. 并发、并行、同步、异步、全局解释锁GIL、同步锁Lock、死锁、递归锁、同步对象/条件、信号量、队列、生产者消费者、多进程模块、进程的调用、Process类、

    并发:是指系统具有处理多个任务/动作的能力. 并行:是指系统具有同时处理多个任务/动作的能力. 并行是并发的子集. 同步:当进程执行到一个IO(等待外部数据)的时候. 异步:当进程执行到一个IO不等到 ...

  9. python 并发编程 多线程 死锁现象与递归锁

    一 死锁现象 所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等 ...

  10. 同步锁,死锁现象与递归锁,信息量Semaphore.....(Day36)

    一.同步锁 三个需要注意的点: #1.线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,其他线程也可以抢到GIL,但如果发现Lock仍然没有被释放则阻塞,即便是拿到执行 ...

随机推荐

  1. zabbix web监测

    web monitoring(监测)属于业务监控,用来监控Web站点多方面的可用性,可以监控Web站点的下载速度.返回码和响应时间.Zabbix能够检测HTML中包含的预先定义的字符串,也可以模拟登录 ...

  2. 倍福TwinCAT(贝福Beckhoff)基础教程5.1 TwinCAT-3 读写注册表

    读写注册表和读写文件一样,里面涉及的输入类型比较复杂,需要参考官方范例 sSubKey是指注册表的路径 sValName是指注册表要写入的名值对的名称 eValType是一个枚举类型(而且不是什么常规 ...

  3. Windows无法删除文件 提示找不到该项目怎么办

    1 如图所示,我想要删除某个文件,提示如图所示,一般用360的强力删除也不管用.   2 在桌面新建一个文本文档,并输入以下内容.保存为bat格式(比如Delete.bat).然后把这个删不掉的文件拖 ...

  4. x^2 + (y-(x^2)(1/3))^2 = 1 心形方程 5.20无聊之作

    2017.05.20 一个无聊的周六,只能看别人秀恩爱.偶然间在网上看到一个有意思的方程 x^2 + (y-(x^2)(1/3))^2 = 1,据说这个方程可以绘制出一个爱心的形状.既然很无聊,就随便 ...

  5. vuex简单示例

    一.vuex是什么,解决了什么问题? 官方解释是:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生 ...

  6. TELNET模拟HTTP请求

    开启nginx服务,查看服务器地址(192.168.11.119) 使用telnet命令连接服务器的80端口 http协议报文格式 1.request 2.response 输入请求行: GET / ...

  7. Oracle 重置序列

    Oracle将自增sequence重置为初始值1时,若是删除再重建,那么依赖它的函数和存储过程将失效,需要重新编译. 这里利用相反的过程,利用负值步长参数,将递增值改为(sequence.NEXTVA ...

  8. php url路由伪静态

    RewriteEngine on RewriteBase /RewriteRule ^([a-zA-Z]{1,})/([a-zA-Z]{1,})$ webim2/operator/users.php? ...

  9. matlab-非线性方程求根函数及函数曲线绘制

    Matlab中提供了很多求解非线性方程(y=f(x))的函数,刚開始使用,真的很困惑.全部.这里依据matlab的help文档对这些函数做一些小小的总结 fsolve函数 用来求解非线性方程组:F(x ...

  10. lvs+keepalived+bind实现负载均衡高可用智能dns

    整体架构: 1.IP地址规划: Dns1:172.28.0.54 Dns2:172.28.0.55 Dr服务器主:172.28.0.57 Dr服务器从:172.28.0.67 Vip:172.28.0 ...