Python多线程核心概念与实战指南:GIL、锁与适用场景深度解析

追风的人
追风的人
发布时间:2026-10-12| 3人浏览过
原创

本文深入解析Python多线程机制,涵盖线程定义、开启方式、join同步、数据共享及守护线程特性。重点剖析CPython中GIL对多核利用的限制,通过互斥锁解决数据竞争,并对比计算密集型与IO密集型场景下的性能差异,为并发编程提供实践指导。

一、什么是线程?

从概念上讲,线程如同一条流水线上的作业流程,而这条流水线必须归属于某个特定的车间,这个车间即为进程。进程的作用是整合资源,它是资源的集合单位;相比之下,线程则是CPU执行任务的具体单位。一个车间(进程)内至少包含一条流水线(线程),但线程才是真正执行计算工作的实体。

总结进程与线程区别:

'''
进程:资源单位
线程:执行单位
线程才是真正干活的人,干活中需要的资源由线程所在进程提供
每个进程肯定自带一个线程
每个进程内可创建多个线程
'''
'''
开进程:
    申请空间
    拷贝代码
    消耗资源大
开线程:
    同一个进程内创建多个线程,无需上述两种操作,消耗资源相对较小
'''

多线程指的是在一个进程内部创建多个控制流。这些控制线程共享该进程拥有的地址空间,这好比一个车间内有多条流水线共同使用车间的资源。因此,多线程实现了资源的高效共享与并发执行。

二、开启线程的两种方式

1、方式1

from threading import Thread
import time
# 方法一
def task(name):
    print('%s is running' % name)
    time.sleep(1)
    print('%s is over' % name)
# 开启线程不需要在main下面执行代码,直接书写就可以
# 但是习惯性的将启动命令写在main下面
t = Thread(target=task, args=('egon',))
t.start()  # 创建线程的开销非常小,几乎是代码一执行就已经创建了
print('主')
'''

运行结果: egon is running 主 egon is over '''

2、方式2

from threading import Thread
class MyThread(Thread):
    def __init__(self, name):
        # 重写了别人的方法,又不知道别人的方法里有啥,就调用父类的方法
        super().__init__()
        self.name = name
    def run(self):
        print('%s is running' % self.name)
        time.sleep(1)
        print('%s is over' % self.name)
if __name__ == '__main__':
    t = MyThread('egon')
    t.start()
    print('主')
'''

运行结果: egon is running 主 egon is over '''

三、线程对象的jion方法()

熟悉进程操作的读者可能了解join的功能。线程的join方法作用类似,它用于阻塞当前线程,直到目标线程执行完毕后再继续执行后续代码,确保线程执行的顺序性。

from threading import Thread
def task(name):
    print('%s is running' % name)
    time.sleep(1)
    print('%s is over' % name)
if __name__ == '__main__':
    t=Thread(target=task,args=('egon',))
    t.start()
    t.join()# 主线程等待子线程运行结束后再执行
    print('主')

''' 运行结果: egon is running egon is over 主 '''

补充一个知识点:同一个进程下的多个线程数据共享,下面为大家举一个简单的案例

from threading import Thread
money=100
def task():
    global money
    money=66
if __name__ == '__main__':
    t=Thread(target=task,args=())
    t.start()
    print(money)

# 结果:66

四、 补充小案例

from threading import Thread
import os,time
def task():
    print('子 pid:',os.getpid())
if __name__ == '__main__':
    t=Thread(target=task,args=())
    t.start()
    print('主 pid:',os.getpid())
    # 两个线程的pid号一样,说明在同一个进程下

''' 运行结果: 子 pid: 13444 主 pid: 13444 '''

# 这是个容易混淆的案例
from threading import Thread,current_thread,active_count
import os,time
def task(n):
    print('子',current_thread().name)
    time.sleep(n) # 延长线程存活时间
if __name__ == '__main__':
    t=Thread(target=task,args=(1,))
    t1=Thread(target=task,args=(1,))
    t.start()
    t1.start()
    t.join()
    # print('主',current_thread().name)# 获取线程名字
    print(active_count()) # 统计当前活跃的进程数

''' 运行结果: 子 Thread-1 子 Thread-2 1 ''' # 这里大家容易以为是3,其实运行后只有一个线程在活跃了,其它两个线程运行完后就停止运行了

五、守护线程

守护线程的概念与守护进程类似。正如前文所述,进程与线程在许多知识点和用法上具有相通性,理解了其中一个概念,另一个也易于掌握。

1、守护线程会随着主线程的结束而结束

2、主线程运行结束后不会立刻结束,会等待所有的其它非守护线程结束后才会结束

3、因为主线程的结束意味着所在进程的结束

from threading import Thread
import time
def task(name):
    print('%s is running'%name)
    time.sleep(1)
    print('%s is over'%name)
if __name__ == '__main__':
    t=Thread(target=task,args=('egon',))
    t.daemon=True #将t设置为守护线程
    t.start()
    print('主')

''' 运行结果: egon is running 主 '''

# 稍微有点迷惑性的例子
from threading import Thread
import time
def foo():
    print('1234')
    time.sleep(1)
    print('end1234')
def func():
    print('5678')
    time.sleep(3)
    print('end5678')
if __name__ == '__main__':
    t1=Thread(target=foo,args=())
    t2=Thread(target=func,args=())
    t1.daemon=True # t1设为守护线程,t2为非守护线程
    t1.start()
    t2.start()
    print('主......')

''' 运行结果: 1234 5678主...... end1234 end5678 ''' ''' 因主线程会等待非守护线程运行结束后在结束, 所有主线程会等待t2(非守护线程)结束再结束, '''

六、线程互斥锁

当多个线程同时操作同一份数据时,可能会引发数据错乱的问题,即竞争条件。

针对上述问题,解决方式就是加锁处理,通过互斥锁保证同一时刻只有一个线程能修改共享数据。

from threading import  Thread,Lock
import time
money=100
mutex=Lock()
def task():
    global money
    mutex.acquire()
    tmp=money
    time.sleep(0.1)# 模拟网络延迟
    money=tmp-1
    mutex.release()
if __name__ == '__main__':
    t_list=[]
    for i in range(100):
        t=Thread(target=task,args=())
        t.start()
        t_list.append(t)
    for t in t_list:
        t.join()
    print(money)

# 运行结果:0 # 多个人操作同一份数据,数据错乱,加锁处理

七、GTL-全局解释器

相信学python的小伙伴都知道,python解释器其实有多个版本

  • Cpython
  • Jpython
  • Pypython

但是普遍使用的都是Cpython解释器

在Cpython解释器中GIL是一把互斥锁,用来阻止同一个进程下的多个线程的同时执行

要注意同一进程下的多个线程无法利用多核优势!!!!

想必大家心中也有不少疑惑:pyhon的多线程是不是一点用都没了呢????

因为Cpython中的内存管理不是线程安全的。多线程并不是一无是处的,在遇到多IO操作的时候,多核的优势也会显示不出来,多进程与多线程的效率在该情况下差不了多少,而此时多进程相对浪费资源,多线程更加节省资源

ps:内存管理就是垃圾回收机制:

1、引用计数

2、标记清除

3、分带回收

# GTL-全局解释器
# 重点:1、GIL不是python的特点而是Cpython解释器的特点
#      2、GIL是保证解释器级别的数据的安全
#      3、GIL会导致同一个进程下的多个线程无法同时进行(即无法利用多核优势)
#      4、针对不同的数据还是需要加不同的锁处理
#      5、解释型语言的通病,同一个进程下多个线程无法利用多核优势

多线程是否有用要看具体情况

八、验证多线程与多线程运用场景

# 计算密集型(CPU一直工作,也没有IO)(更适合多进程)
from multiprocessing import Process
from threading import Thread
import os,time
# 多进程情况
def work():
    res=0
    for i in range(0,10000000):
        res*=i
if __name__ == '__main__':
    l=[]
    print(os.cpu_count())# 获取当前计算机CPU核数
    start_time=time.time()
    for i in range(8):# 我计算机是8核
        p= Process(target=work,args=())
        p.start()
        l.append(p)
    for p in l:
        p.join()
    print(time.time()-start_time)

''' 运行结果: 8 2.0726492404937744 '''

# 多线程情况
from multiprocessing import Process
from threading import Thread
import os,time
def work():
    res=0
    for i in range(0,10000000):
        res*=i
if __name__ == '__main__':
    l=[]
    print(os.cpu_count())# 获取当前计算机CPU核数
    start_time=time.time()
    for i in range(8):# 我计算机是8核
        t=Thread(target=work,args=())
        t.start()
        l.append(t)
    for p in l:
        p.join()
    print(time.time()-start_time)

''' 运行结果: 8 3.5790603160858154 ''' # 显然可知:计算密集型更时候多进程

# IO密集型(任务一直有IO)(多线程更合适)
from multiprocessing import Process
from threading import Thread
import os,time
# 多线程
def work():
    time.sleep(1)
if __name__ == '__main__':
    l=[]
    start_time=time.time()
    for i in range(40):
        t=Thread(target=work,args=())
        t.start()
        l.append(t)
    for p in l:
        p.join()
    print(time.time()-start_time)
# 运行结果:1.0205152034759521
# 多进程
from multiprocessing import Process
from threading import Thread
import os,time
def work():
    time.sleep(1)
if __name__ == '__main__':
    l=[]
    start_time=time.time()
    for i in range(40):
        p= Process(target=work,args=())
        # t=Thread(target=work,args=())
        # t.start()
        # l.append(t)
        p.start()
        l.append(p)
    for p in l:
        p.join()
    print(time.time()-start_time)

# 运行结果:5.927189588546753 # 显然可知:IO密集型更适合多线程

总结:

多线程和多进程都各自有各自的优势

并且在后面的项目中通常可以多进程下面再开设多线程

这样的话我们可以利用多核也可以节省资源消耗

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注本站的更多内容!

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。