Python多线程跟GIL锁到底什么关系?看完你肯定有收获

本文深入探讨Python中多线程的原理与应用,包括全局解释器锁(GIL)的影响,以及在计算密集型和I/O密集型任务中多线程的表现。通过实验对比,展示在不同场景下多线程的优势与局限。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、多线程

线程是操作系统中最小的单位,也是处理器主要消耗者,线程不具备自己的独立资源,一个进程空间中可以同时创建多个线程,并且多个线程之间可以互相操作,一个线程可以启动另外一个线程等,资源也可以共享,线程之间是可以并行运行的(前提是建立在多核CPU,如果是单核则不是真正的并行)。

想了解进程,线程,协程之间的关系可以看看我的另外一篇文章:https://blog.youkuaiyun.com/qq_35869630/article/details/105747155

二、Python中的多线程

1、全局解释器锁(GIL)

相信很多人都听说过这个东西,GIL锁。那这个东西是什么呢?借鉴了某乎上的大佬的回答

Python代码的执行由Python虚拟机(解释器)来控制。
Python在设计之初就考虑要在主循环中,同时只有一个线程在执行,也就是在任何时刻,Python处理器只会处理某一个线程,GIL锁的作用就是来保证整个程序中同时只能有一个程序在运行。

2、单核CPU的运算切换

我们都知道,一般情况下4核CPU运行4个线程,这时他们都是并行的,但是Python不一样,就算你有40个核,Python在处理计算密集型的程序的时候也只会在一个核上运行,让我们看到像是并发操作的不过是Python会不断切换任务进行运行。比如每个线程运行10ms之后切换另一个线程,这样也能让结果就像是正常多线程一样,基本同时跑完任务。

如果你的线程中是计算密集型的程序,这样的多线程不止不能加快运行,反正多增加了CPU切换线程的时间,变得更慢了。

3、IO型多线程的好处

那么这样的话Python多线程是不是就一点用都没有?

有的人可能试过有时候用多线程还是真的能省下翻倍的时间,那不是有GIL锁吗,这是什么情况?
在这里插入图片描述在这里插入图片描述

原来是GIL锁在面对多线程的IO操作的时候,GIL会在调用IO操作的时候释放,允许在等待IO的时候可以运行其他线程。
也就是说,I/O密集型的Python程序比计算密集型的Python程序更能充分利用多线程的好处。
通俗的解释一下就是在线程进行IO操作的时候,CPU会切换到其他线程操作,但是GIL锁并不会使得原来那个线程停止IO,还是会继续进行IO操作的,这样就能加快程序的运行了。

三、实际操作Python多线程

我就知道如果不实际操作一遍,怎么能验证是不是这样呢?

服务器背景 :32g 16核

直接上代码

import pandas as pd


def get_slice_thread_cpu(num):
    res_df = 0
    import concurrent.futures
    with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:
        future_list = {executor.submit(get_df_thread_cpu) for i in range(num)}
        for future in concurrent.futures.as_completed(future_list):
            data = future.result()
            res_df += data
    return res_df


def get_slice_thread_io(num):
    res_df = pd.DataFrame([])
    import concurrent.futures
    with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
        future_list = {executor.submit(get_df_thread_io) for i in range(num)}
        for future in concurrent.futures.as_completed(future_list):
            data = future.result()
            res_df = res_df.append(data)
    return res_df


def get_df_thread_io():
    sql = """
            SELECT *
             from test
                """
    data = get_data_frame(sql, conn)
    return data


def get_df_thread_cpu():
    res = 0
    for i in range(10000000):
        res+=i
    return res

本次试验采用控制变量法= =
1、普通串行运行单个线程的时候 循环计算或者读取数据40次。
2、使用20个线程并发计算或者读取数据。

if __name__ == '__main__':
    from time import time
    start = time()
    num = 40
    for i in range(num):
        df = get_df_thread_cpu()
    print('get_df_thread_cpu Time: {:5.2f}s'.format(time() - start))
    print('end~~~~~~~~~~~~~~',df)
    start = time()
    df_1 = get_slice_thread_cpu(num)
    print('get_slice_thread_cpu Time: {:5.2f}s'.format(time() - start))
    print('end~~~~~~~~~~~~~~',df_1)

查看后台是否只有一个核在运行,不管是在单线程还是多线程的情况下,都是只有一个核在进行计算。
在这里插入图片描述

循环40次,计算密集型结果如下
在这里插入图片描述
可以看到40次循环累计总时长要21.48s,即一个任务平均是0.537s
40次任务放到最大线程数为40的线程池中,一共运行时间为22.01s,平均一个线程0.55025s
虽然一个任务很快,不到1s,但是也可以看出来在密集型计算中并不会真正的进行并发计算,反而会增加cpu切换运行的时间。

from time import time
num = 40
start = time()
for i in range(num):
    df = get_df_thread_io()
print('get_df_thread_cpu Time: {:5.2f}s'.format(time() - start))
print('end~~~~~~~~~~~~~~')
start = time()
df_1 = get_slice_thread_io(num)
print('get_slice_thread_cpu Time: {:5.2f}s'.format(time() - start))
print('end~~~~~~~~~~~~~~')

查看后台cpu运算情况
在这里插入图片描述
还是只有一个核在运行。

执行结果如下
在这里插入图片描述
可以看到在多线程读取数据库数据的时候,明显是有加快的。
单线程下单个任务要0.1125s
多线程下单个任务要0.1062s
这个也跟我们上面的分析是一致的,在面对IO操作的时候,Python多线程还是有起到并发的作用的。

注:注意这里如果IO操作的是数据库的话要注意查询语句的快慢问题,太慢的话可能会卡到相关的数据库,而且高并发本身就会考验数据库的性能,因此设置多线程的时候要注意设置好最大线程数,如果最大线程数太大反而会降低查询效率,因此并不是多线程就一定能加快查询效率的,同时也要注意性能的瓶颈问题。

我是一只前进的蚂蚁,希望能一起前行。

如果对您有一点帮助,一个赞就够了,感谢!

注:如果本篇博客有任何错误和建议,欢迎各位指出,不胜感激!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值