进击のpython

*****

并发编程——GIL全局解释锁


这小节就是有些“大神”批判python语言不完美之处的开始

这一节我们要了解一下Cpython的GIL解释器锁的工作机制

掌握一下GIL和互斥锁

最后再了解一下Cpython下多线程和多进程各自的应用场景

首先需要明确的一点就是GIL不是Python的特性

他是实现Python解释器(Cpython)时所引入的一个概念

当然Python不止这一个解释器来编译代码

只是因为Cpython是大部分默认环境下的Python执行环境

所以在很多人的概念里CPython就是Python

也就想当然的把GIL归结为Python语言的缺陷

所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL


GIL介绍

其实GIL本质上就是一把互斥锁,既然是互斥锁,那么所有的互斥锁本质都一样的

都是将并发编程变成串行,以此来控制同一时间内共享数据只能被一个任务所修改

进而来保证数据的安全,可以肯定的一点是:保护不同数据的安全,就应该加不同的锁

要想了解GIL,首先可以肯定一点的就是:每次执行一个py文件,都会产生一个独立的进程

比如运行1.py 2.py 3.py 就会开三个进程,而且是开三个不同的进程

在一个python的进程中,不仅是有主线程,还应该有开启的其他线程,比如垃圾回收机制级别的线程

但是这些线程都是在这个进程当中运行的,这个无需多言

而前面我们也提到了,线程之间的数据是共享的,既然数据是共享的

代码,其实本身也是数据,也是被所有线程共享的,这其中也包括解释器的代码

而程序在执行之前,需要先执行编译器的代码(这很好理解,否则你的代码仅仅是字符串)

那执行编译器的代码是不是也需要保证编译器的代码安全

所以为了保证代码的安全,我们在编译器上加了一把锁,这把锁就是GIL全局解释锁

而加了这把锁就意味着什么?就意味着python解释器同一时间只能执行一个任务代码

这样就不会出现垃圾回收代码和用户代码同时操作一个变量导致逻辑混乱的问题


GIL与Lock

那既然都已经有一把锁,来保证多线程只能一个一个运行的状态

那为什么还要有Lock这个方法呢?有过这个疑问吗?

还是那句话,加锁的目的是为了保护共享的数据,保证同一时间只能有一个线程来修改共享的数据

进而我们就应该得出结论:保护不同的数据就应该加不同的锁

那问题就变得很清晰了,GIL和Lock是两把锁,保护的数据是不一样的

前者保护的是解释器级别的代码,比如垃圾回收机制啊什么的

但是后面的则是保护的自己开发的应用程序的数据,GIL是不负责的

只能用户自己定义然后加锁处理

如果有100个线程来抢GIL锁

一定有一个线程A先抢到了GIL,然后就开始执行了,只要执行就会拿到lock.acquire()

很可能在A还没运行完,另一个线程B抢到了GIL锁,然后开始运行,看到lock没有被释放,于是就进行阻塞

阻塞的同时就会被迫交出GIL,直到A重新抢到GIL,从上次暂停的位置继续执行,直到正常释放互斥锁lock

举个例子吧:

from threading import Thread, Lock
import os, time def work():
global n
lock.acquire()
temp = n
time.sleep(0.1)
n = temp - 1
lock.release() if __name__ == '__main__':
lock = Lock()
n = 100
l = []
for i in range(100):
t = Thread(target=work)
l.append(t)
t.start()
for t in l:
t.join()
print(n)

打印的结果一定是 0 因为共享数据被保护了,只能一个一个执行


GIL与多线程

问题又出现了,进程呢,是可以利用多核,但是时间长,开销大

python的多线程开销是小,但是由于GIL的原因,不能利用多核优势

这就是在小节刚开始提的批判的‘不完美’之处

在解决这个问题之前,应该对一些问题达成共识!

CPU到底是干啥的呢?是用来计算的还是用来处理I/O阻塞的呢?

很明显是处理计算的,多个CPU是用来处理多个计算任务,换句话说,多个CPU是提高计算速度

但是当CPU遇到I/O阻塞的时候,还是需要等待的,所以,多CPU对处理阻塞没什么用

如果你的工厂是处理石材的,那工人越多效率越快(MC玩多了)

但是,如果你是等待石材过来再加工的,那等待的过程,有多少工人也没用

工人就是CPU,第一个例子就是计算密集型!第二个例子是I/O密集型!

从上面就可以看出来

对计算来说,CPU越多越好,但是对于I/O来说,再多的CPU也没用

但是,没有纯计算和纯I/O的程序,所以我们只能相对的去看一个程序到底是什么类型

所以解决问题是这样的:

方案一:开启多进程

方案二:开启多线程

单核

如果是计算密集型,没有多核的来并行计算,方案一增加了创建进程的开销

如果是I/O密集型,方案一创建的进程开销大,所以还是要选择方案二

多核

如果是计算密集型,在python中同一时刻只能一个线程执行,用不到多核,所以应该选择方案一

如果是I/O密集型,核就没用了,所以应该用方案二

但是很明显现在的计算机都是多核,python对于计算密集型的任务开多线程并不能提高效率

甚至有时候都比不上串行,但是要是对于I/O密集型任务,还是有显著提升的


性能测试

上面说的这么热闹,下面就来亲自试验一下

1.如果并发的多个任务是计算密集型:多进程效率高

from multiprocessing import Process
from threading import Thread
import os,time
def work():
res=0
for i in range(100000000):
res*=i
if __name__ == '__main__':
l=[]
print(os.cpu_count()) #本机为4核
start=time.time()
for i in range(4):
p=Process(target=work) #耗时5s多
p=Thread(target=work) #耗时18s多
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start))

如果并发的多个任务是I/O密集型:多线程效率高

from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
time.sleep(2)
print('===>')
if __name__ == '__main__':
l=[]
print(os.cpu_count()) #本机为4核
start=time.time()
for i in range(400):
# p=Process(target=work) #耗时12s多,大部分时间耗费在创建进程上
p=Thread(target=work) #耗时2s多
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start))
  1. 多线程用于IO密集型,如socket,爬虫,web
  2. 多进程用于计算密集型,如金融分析

*****
*****

~~并发编程(十一):GIL全局解释锁~~的更多相关文章

  1. 10 并发编程-(线程)-GIL全局解释器锁&死锁与递归锁

    一.GIL全局解释器锁 1.引子 在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势 首先需要明确的一点是GIL并不是Python的特性,它是在实现Pyt ...

  2. python 并发编程 多线程 GIL全局解释器锁基本概念

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

  3. GIL全局解释锁,死锁,信号量,event事件,线程queue,TCP服务端实现并发

    一.GIL全局解释锁 在Cpython解释器才有GIL的概念,不是python的特点 在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势. 1.GIL介绍 ...

  4. 20191031:GIL全局解释锁

    20191031:GIL全局解释锁 总结关于GIL全局解释锁的个人理解 GIl全局解释锁,本身不是Python语言的特性,而是Python语言底层的c Python解释器的一个特性.在其他解释器中是没 ...

  5. python网络编程--线程GIL(全局解释器锁)

    一:什么是GIL 在CPython,全局解释器锁,或GIL,是一个互斥体防止多个本地线程执行同时修改同一个代码.这把锁是必要的主要是因为当前的内存管理不是线程安全的.(然而,由于GIL存在,其他特性已 ...

  6. GIL全局解释锁

    目录 一 介绍 二 GIL介绍 三 GIL与多线程 四 多线程性能测试 一 介绍 ''' 定义: In CPython, the global interpreter lock, or GIL, is ...

  7. python中的GIL(全局解释锁)多线程能够提升效率

    预启动的时候,应用程序仍然会调用 OnLaunched 方法的,在 OnLaunched 方法调用之后,会马上发生 Suspending 事件,随后应用就会暂停. 我先基于develop主分支拉出一个 ...

  8. python GIL全局解释器锁与互斥锁 目录

    python 并发编程 多线程 GIL全局解释器锁基本概念 python 并发编程 多线程 GIL与Lock python 并发编程 多线程 GIL与多线程

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

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

随机推荐

  1. String类基础知识

    1.String类的构造方法 (1)String(String original)  //把字符串数据封装成字符串对象 (2)String(char[] c)   //把字符数组的数据封装成字符串对象 ...

  2. python黑帽子之tcp服务端

    试着用python创建一个标准的多线程tcp服务器 import socket import threading bind_ip = "0.0.0.0" bind_port = 8 ...

  3. CodeForces 3 D.Least Cost Bracket Sequence【贪心+优先队列】

    Description 给出一个括号序列,中间有一些问号,将第i个问号换成左括号代价是a[i],换成右括号代价是b[i],问如果用最少的代价将这个括号序列变成一个合法的括号序列 Input 第一行一个 ...

  4. python之类与对象(一)

    1.改变对象的字符串显示,要改变一个实例的字符串表示,可重新定义它的 str () 和 repr () 方法 class Pair: def __init__(self, x, y): self.x ...

  5. HTML5(八)Web Workers

    HTML 5 Web Workers web worker 是运行在后台的 JavaScript,不会影响页面的性能. 什么是 Web Worker? 当在 HTML 页面中执行脚本时,页面的状态是不 ...

  6. MySQL CodeFirst的配置与注意事项

    mysql+ef的配置相比较mssql+ef来说复杂一些.我的感受就是配置难度在于插件版本造成的各种不兼容问题.另外参考了很多博客,将多个博客里的经验综合才得以实现,因为不是每个人的操作都和那些博客作 ...

  7. IIFE中的函数是函数表达式,而不是函数声明

    下面的代码打印什么内容,为什么? var b = 10; (function b(){ b = 20; console.log(b); })(); 针对这题,在知乎上看到别人的回答说: 函数表达式与函 ...

  8. java 面向对象(二十):类的结构:代码块

    类的成员之四:代码块(初始化块)(重要性较属性.方法.构造器差一些)1.代码块的作用:用来初始化类.对象的信息2.分类:代码块要是使用修饰符,只能使用static分类:静态代码块 vs 非静态代码块3 ...

  9. 02 drf源码剖析之快速了解drf

    02 drf源码剖析之快速了解drf 目录 02 drf源码剖析之快速了解drf 1. 什么是drf 2. 安装 3. 使用 3. DRF的应用场景 1. 什么是drf drf是一个基于django开 ...

  10. Angular 懒加载找不到模块问题解决方法

    问题: 懒加载无法找到模块 解决办法: 在app-routing.module.ts中引入该模块