进击の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. Eclipse配置maven环境1

    一.什么是maven? Maven是一个项目管理工具,它包含了一个项目对象模型 (Project Object Model),一组标准集合,一个项目生命周期(Project Lifecycle),一个 ...

  2. 《UNIX环境高级编程》(APUE) 笔记第八章 - 进程控制

    8 - 进程控制 Github 地址 1. 进程标识 每个进程都有一个非负整型表示的 唯一进程 ID .进程 ID 是可复用的(延迟复用算法). ID 为 \(0\) 的进程通常是调度进程,常常被称为 ...

  3. Nginx 从入门到放弃(一)

    Nginx nginx的使用场景 静态资源服务 通过本地文件系统提供服务 反向代理服务 nginx的强大性能 缓存 负载均衡 API服务 OpenResty nginx优点 高并发.高性能 可扩展性好 ...

  4. [SCOI2016]背单词 题解

    背单词 https://www.luogu.com.cn/problem/P3294 前言: Trie树的省选题(瑟瑟发抖QAQ) 问题汇总:(请忽略) (1)对Trie字典树的运用不熟练 (2)没想 ...

  5. Orleans 框架3.0 官方文档中文版系列一 —— 概述

    关于这个翻译文档的一些说明: 之前逛博客园的时候,看见有个园友在自己的博客上介绍Orleans. 觉得Orleans 是个好东西. 当时心想:如果后面有业务需要的时候可以用用Orleans框架. 当真 ...

  6. node 模块正确暴露方法

    一个node模块,为了能够服用,就需要将其暴露,那么如何正确写呢?(参考:https://developer.mozilla.org/zh-CN/docs/Learn/Server-side/Expr ...

  7. Solaris 10上Oracle 10g安装步骤图解

    文章目录 1. 说明 2. 查看相关包 3. 添加用户和组 4. 设置oracle环境变量 5. 创建Oracle软件目录 6. 修改OS参数 7. 上传Oracle软件包并解压 8. 开始安装 9. ...

  8. 从Linux源码看Socket(TCP)Client端的Connect

    从Linux源码看Socket(TCP)Client端的Connect 前言 笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情. 今天笔者就来从Linux源码的 ...

  9. 「疫期集训day10」玫瑰

    不管我们在怎么抵抗,德国都已经败了----失守苏瓦松后绝望中的德国兵 (貌似今天的题记和内容毫无关系) 觉得以后还是不要抱怨考试失误了,感觉没啥大用 T1暴搜/状压(然俄一看题很像刚写过的二分答案,上 ...

  10. 真懂Spring的@Configuration配置类?你可能自我感觉太良好

    当大潮退去,才知道谁在裸泳.关注公众号[BAT的乌托邦]开启专栏式学习,拒绝浅尝辄止.本文 https://www.yourbatman.cn 已收录,里面一并有Spring技术栈.MyBatis.中 ...