GIL锁

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

全局解释器锁(GIL)

​ Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。

对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。

在多线程环境中,Python 虚拟机按以下方式执行:

1、设置 GIL;

2、切换到一个线程去运行;

3、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));

4、把线程设置为睡眠状态;

5、解锁 GIL;

6、再次重复以上所有步骤。

在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。

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,JPython,PyPy,Psyco等不同的Python执行环境来执行。而JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL。

线程锁(互斥锁)1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28from threading import Thread

import time

def ():

# 如果这里直接对num进行运算很难出现数据不安全的结果

global num # 把num变成全局变量

temp = num

time.sleep(1) # 注意了sleep的时候是不占有cpu的,这个时候cpu直接把这个线程挂起了,此时cpu去干别的事情去了

num = temp + 1 # 所有的线程都做+1操作

if __name__ == '__main__':

num = 0 # 初始化num为0

t_obj = list()

for i in range(100):

t = Thread(target=work)

t.start()

t_obj.append(t)

for t in t_obj:

t.join()

print("num:", num) # 输出最后的num值,可能是1

# 执行结果

num: 1

下面我们就用一张图来解释一下这个原因 :

上面的例子中出现数据不安全问题,那么我们应该怎么解决呢?在这里我们引用线程锁来解决这个问题

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31from threading import Lock,Thread

import time

def (lock):

# 如果这里直接对num进行运算很难出现数据不安全的结果

global num # 把num变成全局变量

lock.acquire()

temp = num

time.sleep(0.1) # 注意了sleep的时候是不占有cpu的,这个时候cpu直接把这个线程挂起了,此时cpu去干别的事情去了

num = temp + 1 # 所有的线程都做+1操作

lock.release()

if __name__ == '__main__':

num = 0 # 初始化num为0

t_obj = list()

lock = Lock()

for i in range(100):

t = Thread(target=work, args=(lock,))

t.start()

t_obj.append(t)

for t in t_obj:

t.join()

print("num:", num) # 输出最后的num值

# 执行结果

num: 100

注意:这里的Lock创建的锁和GIL没有关系 ,

递归锁(RLock)

线程死锁

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40from threading import Lock, Thread

import time

noodle_lock = Lock()

fork_lock = Lock()

def eat1(name):

noodle_lock.acquire()

print('%s拿到面条啦' % name)

fork_lock.acquire()

print('%s拿到叉子了' % name)

print('%s吃面' % name)

fork_lock.release()

noodle_lock.release()

def eat2(name):

fork_lock.acquire()

print('%s拿到叉子了' % name)

time.sleep(1)

noodle_lock.acquire()

print('%s拿到面条啦' % name)

print('%s吃面' % name)

noodle_lock.release()

fork_lock.release()

if __name__ == '__main__':

Thread(target=eat1, args=('张三',)).start()

Thread(target=eat2, args=('李四',)).start()

Thread(target=eat1, args=('王五',)).start()

Thread(target=eat2, args=('赵六',)).start()

# 执行结果

张三拿到面条啦

张三拿到叉子了

张三吃面

李四拿到叉子了

王五拿到面条啦

上面例子中情况是在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。执行结果,是无限的进入死循环,所以不能这么加,这个时候就需要用到递归锁。

递归锁(RLock)1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32from threading import Thread, RLock # 递归锁

import time

fork_lock = noodle_lock = RLock() # 一个钥匙串上的两把钥匙

def eat1(name):

noodle_lock.acquire() # 一把钥匙

print('%s拿到面条啦' % name)

fork_lock.acquire()

print('%s拿到叉子了' % name)

print('%s吃面' % name)

fork_lock.release()

noodle_lock.release()

def eat2(name):

fork_lock.acquire()

print('%s拿到叉子了' % name)

time.sleep(1)

noodle_lock.acquire()

print('%s拿到面条啦' % name)

print('%s吃面' % name)

noodle_lock.release()

fork_lock.release()

if __name__ == '__main__':

Thread(target=eat1, args=('张三',)).start()

Thread(target=eat2, args=('李四',)).start()

Thread(target=eat1, args=('王五',)).start()

Thread(target=eat2, args=('赵六',)).start()

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

注意:递归锁用于多重锁的情况,如果只是一层锁,就用不上递归锁

python中gil锁和线程锁_Python线程——GIL锁、线程锁(互斥锁)、递归锁(RLock)...相关推荐

  1. python 多线程 数据库死锁_python并发编程之多线程2死锁与递归锁,信号量等

    一.死锁现象与递归锁 进程也是有死锁的 所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用, 这些永远在互相等待的进程称为死锁进程 如下就是死锁 ...

  2. python中最难的是什么_Python 最难的问题你猜是什么?

    超过十年以上,没有比解释器全局锁(GIL)让Python新手和专家更有挫折感或者更有好奇心. 未解决的问题 随处都是问题.难度大.耗时多肯定是其中一个问题.仅仅是尝试解决这个问题就会让人惊讶.之前是整 ...

  3. python中多行注释可以嵌套单行注释吗_Python单行注释与多行注释

    >>> print "hello,world" hello,world >>> 2+2 4 #单行注释 """每行 ...

  4. python中多线程是真的还是假的_Python中的鸡肋多线程

    在介绍Python中的线程之前,先明确一个问题,Python中的多线程是假的多线程! 为什么这么说,我们先明确一个概念,全局解释器锁(GIL). Python代码的执行由Python虚拟机(解释器)来 ...

  5. python协程处理多个文件_python:多任务(线程、进程、协程)

    一.线程 1.创建线程 #创建线程 importthreading,timedeftask1():for i in range(5):print('task1 -- 任务:%s' %i) time.s ...

  6. python中用于获取当前目录的是_python中获得当前目录和上级目录的实现方法

    python中获得当前目录和上级目录的实现方法 获取当前文件的路径: from os import path d = path.dirname(__file__) #返回当前文件所在的目录 # __f ...

  7. python中组合与继承的区别_python类与对象的组合与继承

    1.把类的对象实例化放到一个新的类里面叫做类的组合,组合就是指几个横向关系的类放在一起,纵向关系的类放在一起是继承,根据实际应用场景确定.简单的说,组合用于"有一个"的场景中,继承 ...

  8. python3 线程隔离_Python并发编程之线程中的信息隔离(五)

    大家好,并发编程 进入第三篇. 上班第一天,大家应该比较忙吧.小明也是呢,所以今天的内容也很少.只要几分钟就能学完. 昨天我们说,线程与线程之间要通过消息通信来控制程序的执行. 讲完了消息通信,今天就 ...

  9. python中列表是什么样的数据结构_Python中列表、字典、元组、集合数据结构整理...

    Python常见数据结构整理 Python中常见的数据结构可以统称为容器(container).序列(如列表和元组).映射(如字典)以及集合(set)是三类主要的容器. 一.序列(列表.元组和字符串) ...

  10. python中不可迭代对象有哪些_python可迭代对象

    本身实现了迭代方法的对象称之为可迭代对象,可迭代对象特点: 支持每次返回自己所包含的一个成员的对象: 对象实现了 __iter__ 方法: 所有数据结构都是可迭代对象: for 循环要求对象必须是一个 ...

最新文章

  1. Preview is unavailable until a successful build
  2. 固态硬盘uefi装win10
  3. “天下第一长联”与“元跨革囊”
  4. 正则 null_正则表达式exec、match、test的区别
  5. android的AIDL的调用
  6. ManjarorLinux操作笔记
  7. thread和threadLocal之间的关系
  8. 黑苹果efi制作_黑苹果微星B450AMD完美方案分享包括EFI制作工具及教程
  9. java pcm to wav_Java音频转换:PCM格式转WAV格式
  10. 华为android9使用外置存储卡,华为mate9如何删除内存卡文件?华为手机清理内存教程...
  11. 跨专业北邮计算机考研,北京邮电大学跨专业考研心得
  12. 廊坊金彩:店铺如何分析问题
  13. 历史小说《雍正皇帝》后感(电视剧雍正王朝)
  14. 哪些权重7权重8的网站怎么做的!我的站就是这么做的!轻量级泛目录无需数据库适合所有网站所有cms只需要放在根目录即可
  15. springboot连接mysql8.x: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents
  16. MyEclipse全局搜索
  17. 台达 PLC 浮点数 乘法和除法
  18. SSD异常掉电数据是否会损坏
  19. [转] volatile关键字解析
  20. pandorabox挂硬盘文件服务器,PandoraBox将系统挂载到U盘启动图文教程

热门文章

  1. 脉脉上发匿名消息,拼多多员工被开除了!
  2. 我生于1997,我骄傲了吗?
  3. 这是一名南京985AI硕士,CSDN博客专家
  4. 一文带你看懂Springboot核心功能及优缺点
  5. 彻底理解 Cookie,Session,Token
  6. 为什么说++i的效率比i++高?
  7. 论文笔记 | CNN 是怎么学到图片绝对位置信息的
  8. C++——创建类的时候用new与不用new 的区别(转)
  9. Nuxt.js - nuxt-link与router-link的差异
  10. 【从蛋壳到满天飞】JS 数据结构解析和算法实现-AVL树(一)