文章目录

  • 1. Python多线程的缺陷
    • 1.1 Java单线程和多线程执行倒计时函数
    • 1.2 Python单线程和多线程执行倒计时函数
  • 2. GIL
    • 2.1 什么是GIL
    • 2.2 Python为什么不舍弃GIL
  • 3. Python的多线程这么辣鸡,那还用不用?
    • 3.1 多线程
    • 3.2 多进程
    • 3.3 协程
    • 3.4 多进程 VS 多线程 VS 协程

1. Python多线程的缺陷

比如我现在要执行2个倒计时函数,
我可以选择用一个线程顺序执行这2个函数,按顺序倒计时2次;
也可以用两个线程并行这2个函数,也就是同时倒计时。

我的电脑有4个CPU核心,是可以并行4个线程的,因此我并行上述2个线程完全没问题。

也就是说,我用2个线程执行2个倒计时用的时间应该是一个线程执行2次倒计时时间的一半。

1.1 Java单线程和多线程执行倒计时函数

在Java中确实是如此,如下。

Java单进程执行:
如下代码,有一个倒计时函数countDown(),我在主函数里顺序执行两次倒计时500000000下:

public class Main {private static void countDown(long count) {while (count > 0) {count--;}System.out.println("倒计时完成");}public static void main(String[] args) throws InterruptedException {long startTime = System.currentTimeMillis();countDown(500000000);countDown(500000000);long endTime = System.currentTimeMillis();System.out.println("耗时间为:" + (endTime - startTime));}
}

输出:

倒计时完成
倒计时完成
耗时间为:456

Java多线程执行:

public class Main {private static void countDown(long count) {while (count > 0) {count--;}System.out.println("倒计时完成");}public static void main(String[] args) throws InterruptedException {long startTime = System.currentTimeMillis();Thread t1 = new Thread(() -> countDown(500000000));Thread t2 = new Thread(() -> countDown(500000000));t1.start();t2.start();t1.join();t2.join();long endTime = System.currentTimeMillis();System.out.println("耗时间为:" + (endTime - startTime));}
}

输出:

倒计时完成
倒计时完成
耗时间为:274

使用了多线程确实速度变快了很多,花费时间几乎是单线程的一半。(为什么不是严格的一半?因为用了多线程之后,线程之间切换也需要开销)

但是Python的表现却不是这样

1.2 Python单线程和多线程执行倒计时函数

同样的,使用Python来对比单进程和多线程执行倒计时函数,如下:

Python单线程执行:

import time
from threading import Thread
# 倒计时函数
def count_down(count):while count > 0:count -= 1print("倒计时完成")start = time.time()count_down(50000000)
count_down(50000000)end = time.time()
print(f"耗时为:{end -  start}")

输出:

倒计时完成
倒计时完成
耗时为:3.884136199951172

Python多线程执行:

import time
from threading import Thread
# 倒计时函数
def count_down(count):while count > 0:count -= 1print("倒计时完成")start = time.time()t1 = Thread(target=count_down, args=(50000000, ))
t2 = Thread(target=count_down, args=(50000000,))
t1.start()
t2.start()
t1.join()
t2.join()end = time.time()
print(f"耗时为:{end -  start}")

输出:

倒计时完成
倒计时完成
耗时为:4.136513948440552

此时却发现,Python多线程执行用的时间比单线程用的时间还要多??

这是因为Python的多线程并不能并行!

为什么Python的多线程不能并行?

2. GIL

Python在同一时间,只有一个线程可以运行。但是这是为什么,就是Pyhton中的GIL进行了限制!

当线程要想执行,就要获取GIL,但是一个Python进程只有一个GIL,因此同一时刻只有一个线程可以执行。

那么GIL是什么?

2.1 什么是GIL

GIL全称叫做Global Interpreter Lock,翻译过来就是全局解释器锁,那么GIL有什么作用?

GIL的作用是用于Python的内存管理。

Python使用的自动内存管理机制,也就是创建的对象不用了之后就可以自动回收。

引用计数法:

Python使用引用记数法来给每个对象进行标记,每个对象有一个引用计数值count。
如果有变量引用该对象,就会给count +1;
如果一个变量不引用该对象了,就将count -1;
当一个对象的引用计数值count为0,该对象就可以回收了。

如下代码:

import sys
a = [] # a引用了该列表对象, 引用计数为1
b = a # b也引用了该列表对象,引用计数为2
print(sys.getrefcount(a)) # 这里该对象又被该方法的形参引用,引用计数为3

输出:

3

加锁:

也就是当一个变量引用一个对象,需要修改该对象的引用计数值,那么如果是多个进程同时引用某个对象,会同时修改该对象的引用计数值。,有可能就会造成某个对象的引用计数值count不正确,比如:

刚开始某个变量的计数值count是1,现在有两个线程同时引用该对象,也就是同时执行count += 1操作。

count += 1其实是分为三步的:

1. 读取count的值
2. 修改count的值

有可能发生这种情况:

线程1读取count的值,count = 1
线程2读取count的值,count = 1
线程1修改count = 2
线程2修改count = 2

也就是count += 1执行2次,结果应该是3,但是由于多线程同时执行,count修改后最终结果却是2。

而当count == 0时,该对象就会被回收,而上述的多线程同时修改count值的问题,会造成该对象不该回收却被回收了。

为了防止线程安全问题,就需要给每个对象的引用计数值加锁,那么每个对象的引用计数值都需要加锁,不但很麻烦,而且容易造成死锁。

因此Python在设计出来的时候,只设计了一把锁 : GIL,当一个线程需要执行,需要线获取GIL,然后执行,这样就不会有多个线程同时对一个对象的引用计数count进行操作,这样就很安全。

GIL只有1个,线程要获取GIL再执行,因此Python同一时刻只有一个线程可以执行。

既然由于GIL的限制,让Python多线程无法并行,为什么不去掉GIL,或者更改内存管理机制?

注: Python有低层有多种实现,官方的是使用C实现的叫CPython,还是使用Java实现的叫Jython,C#实现的等等,但是只有CPython受到GIL的限制。

2.2 Python为什么不舍弃GIL

Python成也GIL,败也GIL。

GIL导致多线程无法并行,因此是被最多人诟病的一点,但为什么至今CPython还在使用呢?

Python的第三方库非常丰富,使用C写的,由于GIL让同一个时刻只有一个线程执行,保证了线程安全,因此这些库在设计的时候不用考虑线程安全问题,实现起来非常容易。

如果现在修改的话,那么那么库都不能使用了。

并且很多人认为,程序执行效率影响最大的不是CPU而是I/O。

GIL结构简单,效率非常高,如果使用其他的机制,效率会降低,因此GIL至今仍然在使用。

3. Python的多线程这么辣鸡,那还用不用?

在前面那个倒计时的例子中,其实是一个极端的例子。一般程序可以分为三种:

  1. CPU密集型
  2. I/O密集型
  3. CPU和I/O均衡型

上面那个倒计时的例子,是属于CPU密集型,因为全部都是CPU指令。

但是由于GIL,同一时间只有一个线程可以执行,也只能利用到一个CPU的核心。因此对于CPU密集型,使用多线程不是最好的选择。

3.1 多线程

对于多进程来说,创建出多个进程,只不过增加了该程序被执行的概率。

因为操作系统的基本调度单位是线程,一个进程创建出多个线程,多个线程去争抢时间片,因此该进程执行的概率更大(就好比使用多个号参与抽奖,自己中奖的概率更高)。

但是Python的多线程由于GIL又不能利用到多个CPU核心。

3.2 多进程

GIL只是锁住了一个进程,如果使用多进程,就可以解除GIL的限制。

使用多进程的话,不同进程切换开销更大,但是起码多个进程可以并行,可以充分利用CPU的多个核心

因此,对于CPU密集型,可以使用多进程。

还是倒计时的例子,使用多进程代码如下:

import time
from multiprocessing import Pool# 倒计时函数
def count_down(count):while count > 0:count -= 1print("倒计时完成")start = time.time()pool = Pool(processes=2)
p1 = pool.apply_async(count_down, [50000000])
p2 = pool.apply_async(count_down, [50000000])
pool.close()
pool.join()end = time.time()
print(f"耗时为:{end -  start}")

输出:

倒计时完成
倒计时完成
耗时为:2.0172834396362305

可以看到,使用多进程可以实行两个进程并行执行倒计时函数,速度提高了不少。

3.3 协程

对于I/O密集型,多进程和多线程都不是也不一定是最好的选择。

多进程开销大可以充分利用CPU,但是I/O密集型程序执行CPU指令的时间比较少,用不到。

而多线程,需要操作系统参与调度,开销虽然比多进程小,但是还是有点大。

而Python原生实现了协程。

协程比线程更加轻量级,一个线程里面可以创建多个协程,并且协程调度有Python虚拟机来实现,不需要操作系统来参与

对于I/O密集型程序,大部分时间都在等待I/O,CPU执行指令的时间比较少,比如爬虫,因此使用协程来实现比较合适。

3.4 多进程 VS 多线程 VS 协程

多进程 多线程 协程
是否并行 并行 非并行 非并行
调度开销 很大
适合程序类型 CPU密集型 其他 I/O密集型

Python辣鸡,Python多线程不能并行?相关推荐

  1. 辣鸡python导入不了函数库嘤嘤嘤(问题)

    python萌新,用的cmd里pip install函数一直不行,pip已经是最新,python重装也不行. 一个萌新失去了梦想,求助

  2. ios拒审4.3 python自动生成辣鸡代码

    配置文件config.json [{"key" : "jiebabuyuxiniubuyu","add_func_num" :1," ...

  3. python面试题之多线程好吗?列举一些让Python代码以并行方式运行的方法

    答案 Python并不支持真正意义上的多线程.Python中提供了多线程包,但是如果你想通过多线程提高代码的速度,使用多线程包并不是个好主意.Python中有一个被称为Global Interpret ...

  4. Python 多进程开发与多线程开发

    我们先来了解什么是进程? 程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程.程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本:进程 ...

  5. python中的多任务-多线程和多进程

    多线程和多进程都是实现多任务的一种方式,但是对于很多初学者来说想分清楚他们往往是一件非常头疼的事,首先我们需要了解多任务的概念. 所谓的多任务就是在同一时刻同时做很多事情,比如我们一边使用浏览器上网一 ...

  6. 从零开始学 Python 之 初识 Python 多线程

    我们知道,多线程与单线程相比,可以提高 CPU 利用率,加快程序的响应速度. 单线程是按顺序执行的,比如用单线程执行如下操作: 6秒读取文件1 9秒处理文件1 5秒读取文件2 8秒处理文件2 总共用时 ...

  7. 一行 Python 实现并行化 -- 日常多线程操作的新思路 - 左手键盘,右手书 - SegmentFault...

    一行 Python 实现并行化 -- 日常多线程操作的新思路 - 左手键盘,右手书 - SegmentFault

  8. python解决鸡兔同笼问题

    python解决鸡兔同笼问题 参考文章: (1)python解决鸡兔同笼问题 (2)https://www.cnblogs.com/xiaolu915/p/10587499.html 备忘一下.

  9. Python学习笔记:多线程和多进程(转1)

    前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...

最新文章

  1. DL之CNN:计算机视觉之卷积神经网络算法的简介(经典架构/论文)、CNN优化技术、调参学习实践、CNN经典结构及其演化、案例应用之详细攻略
  2. 图像二值化之最大类间方差法(大津法,OTSU)
  3. 关于父窗口获取跨域iframe子窗口中的元素
  4. boost::geometry::index::detail::segment_intersection用法的测试程序
  5. 软件设计过程经验谈 之 如何做好领域模型设计
  6. 统计app用户在线时长_「云工作普及系列」2.如何实时统计工作时长,提高工作效率
  7. mysql删除表单挑数据_MySQL 删除数据表
  8. python输入名字配对情侣网名_输入姓名配对qq网名,QQ情侣昵称
  9. Cover Protocol发起新提案,为Nexus Mutual提供保险覆盖
  10. shell脚本七十问
  11. E: Could not get lock /var/lib/dpkg/lock - open (11: Resource temporarily unavailable)
  12. 机器学习基础 -- 李宏毅2020机器学习课程笔记(一)
  13. SharePoint 2013 母版页修改后,无法添加应用程序
  14. 关于KL距离(KL Divergence)
  15. my-mind在线思维导图软件
  16. redis简略版笔记
  17. linux获取android界面,Android中 adb shell ps 查看手机中进程信息
  18. RK3568平台开发系列讲解(USB篇)libusb流程简介
  19. 三国志战略版:Daniel_平民福音-“黑科技阵法”三势阵
  20. 天天特惠系统秒杀优化方案

热门文章

  1. 黑马程序员--JAVA实战银行排号业务
  2. 游承超:手机贴膜行业者心声:说爱你不容易 (25P)
  3. echarts--设置折线图折点的样式
  4. LS1046A 网卡顺序调整,失败的情况解决
  5. 基于SSH开发网上机票销售系统
  6. 分布式系统的事务处理几种常见方法
  7. 计算机编程 机器人,秒懂机器人编程与计算机编程的区别!
  8. 日常生活中总结出来的DIY生活小窍门
  9. mysql事务如何保证持久性_详解MySQL事务持久性实现
  10. python 创建工具包_创建者工具包–快速创建