Python 学习笔记(十七):并发编程之多线程

本文深入探讨了线程与进程的概念、特点及优劣对比,详细解析了Python中使用threading模块创建线程的方法,包括自定义线程、线程同步互斥机制,并讨论了线程GIL问题对多线程性能的影响。

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

一、线程

线程的基本概念

1. 什么是线程

【1】 线程被称为轻量级的进程
【2】 线程也可以使用计算机多核资源,是多任务编程方式
【3】 线程是系统分配内核的最小单元
【4】 线程可以理解为进程的分支任务

2. 线程特征

【1】 一个进程中可以包含多个线程
【2】 线程也是一个运行行为,消耗计算机资源
【3】 一个进程中的所有线程共享这个进程的资源(变量)
【4】 多个线程之间的运行互不影响各自运行
【5】 线程的创建和销毁消耗资源远小于进程
【6】 各个线程也有自己的ID等特征

3. 线程优点

【1】使用线程可以把占据长时间的程序中的任务放到后台去处理。
【2】用户界面可以更加吸引人,比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。
【3】程序的运行速度可能加快。
【4】在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。


二、使用 threading 模块创建线程

【1】 创建线程对象
from threading import Thread
t = Thread(target, args, kwargs)
功能:创建线程对象
参数:target 绑定线程函数
     args: 元组 给线程函数位置传参
     kwargs 字典 给线程函数键值传参
【2】 启动线程
t.start()
【3】 回收线程
t.join([timeout])
【4】线程对象属性
t.name 线程名称
t.setName() 设置线程名称
t.getName() 获取线程名称
t.is_alive() 查看线程是否在生命周期
t.daemon 设置主线程和分支线程的退出关系
t.setDaemon() 设置daemon属性值
t.isDaemon() 查看daemon属性值,daemon为True时主线程退出分支线程也退出。要在start前设置,通常不和join一起使用。

自定义线程

1. 创建步骤

【1】 继承Thread类
【2】 重写__init__方法添加自己的属性,使用super加载父类属性
【3】 重写 run 方法

2. 使用方法

【1】 实例化对象
【2】 调用start自动执行run方法
【3】 调用join回收线程

3. 示例
from threading import Thread
import time

# 自定义线程类
class MyThread(Thread):
    # 重写父类init
    def __init__(self, target=None, args=None, kwargs=None):
        self.target = target
        self.args = args
        self.kwargs = kwargs
        super().__init__()  # 加载父类init
    # 重写run方法
    def run(self):
        self.target(*self.args, **self.kwargs)

def player(sec, song):
    for i in range(3):
        print("Playing %s : %s" % (song, time.ctime()))
        time.sleep(sec)

t = MyThread(target=player, args=(2,), kwargs={"song": "凉凉"})

t.start()
t.join()
Playing 凉凉 : Mon Aug 12 19:29:21 2019
Playing 凉凉 : Mon Aug 12 19:29:23 2019
Playing 凉凉 : Mon Aug 12 19:29:25 2019

三、同步互斥

基本概念

1. 共享资源争夺

共享资源:多个进程或者线程都可以操作的资源称为共享资源。对共享资源的操作代码段称为临界区。
影响:对共享资源的无序操作可能会带来数据的混乱,或者操作错误。此时往往需要同步互斥机制协调操作顺序。

2. 同步互斥机制

同步 : 同步是一种协作关系,为完成操作,多进程或者线程间形成一种协调,按照必要的步骤有序执行操作。
在这里插入图片描述
互斥 : 互斥是一种制约关系,当一个进程或者线程占有资源时会进行加锁处理,此时其他进程线程就无法操作该资源,直到解锁后才能操作。
在这里插入图片描述

线程同步互斥方法

1. 线程 Event

python线程的事件用于主线程控制其他线程的执行,事件是一个简单的线程同步对象,它主要包括以下几个方法:

  • e = Event(): 创建线程 event 对象
  • e.wait([timeout]): 阻塞等待 e 被 set
  • e.set(): 设置 e,使wait结束阻塞
  • e.clear(): 使 e 回到未被设置状态
  • e.is_set(): 查看当前 e 是否被设置

示例

from threading import Thread, Event, Lock

s = None  # 用于通信
e = Event()
lock = Lock()

def yzr():
    print("杨子荣 前来拜山头")
    global s
    s = "天王盖地虎"
    e.set()  # 操作完共享资源, e 设置, 结束阻塞

t = Thread(target=yzr)
t.start()

print("说对口令就是自己人")
e.wait()  # 阻塞等待 子线程执行

if s == "天王盖地虎":
    print("宝塔镇河妖")
    print("回答正确")
else:
    print("枪毙!")

t.join()
2. 线程锁 Lock
from threading import Lock

lock = Lock() # 创建锁对象
lock.acquire() # 上锁 如果lock已经上锁再调用会阻塞
lock.release() # 解锁

with lock:  # 上锁
    ... 
# with 代码块结束自动解锁

死锁及其处理

1. 定义
死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。
在这里插入图片描述
2. 死锁产生条件

  • 死锁发生的必要条件
    • 互斥条件:指线程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用完释放。
    • 请求和保持条件:指线程已经保持至少一个资源,但又提出了新的资源请求,而该资源
      已被其它进程占有,此时请求线程阻塞,但又对自己已获得的其它资源保持不放。
    • 不剥夺条件:指线程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由
      自己释放,通常CPU内存资源是可以被系统强行调配剥夺的。
    • 环路等待条件:指在发生死锁时,必然存在一个线程——资源的环形链,即进程集合
      {T0,T1,T2,···,Tn} 中的 T0 正在等待一个 T1 占用的资源; T1 正在等待 T2 占用的资源,…,Tn正在等待已被T0占用的资源。
  • 死锁的产生原因
    简单来说造成死锁的原因可以概括成三句话:
    • 当前线程拥有其他线程需要的资源
    • 当前线程等待其他线程已拥有的资源
    • 都不放弃自己拥有的资源

3. 如何避免死锁

  • 死锁是我们非常不愿意看到的一种现象,我们要尽可能避免死锁的情况发生。通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或者几个,来预防发生死锁。预防死锁是一种较易实现的方法。但是由于所施加的限制条件往往太严格,可能会导致系统资源利用率。

四、python线程 GIL

1. python线程的GIL问题 (全局解释器锁)

  • 什么是GIL : 由于python解释器 (CPython) 设计中加入了解释器锁,导致 python 解释器同一时刻只能解释执行一个线程,大大降低了线程的执行效率。

  • 导致后果 : 因为遇到阻塞时线程会主动让出解释器,去解释其他线程。所以python多线程在执行多阻塞高延迟 IO 时可以提升程序效率,其他情况并不能对效率有所提升。

  • GIL问题建议

    1.如果要使用计算机的多核资源,尽量使用多进程而不使用多线程,比如使用multiprocessing 代替 threading,每个进程有自己的独立的GIL,因此也不会出现进程之间的 GIL争抢。

    2.不使用 c 作为解释器;

2. 结论 :

  • 在无阻塞状态下,多线程程序和单线程程序执行效率几乎差不多,甚至还不如单线程效率。但是多进程运行相同内容却可以有明显的效率提升。

五、进程线程的区别联系

1. 区别联系

  1. 两者都是多任务编程方式,都能使用计算机多核资源
  2. 进程的创建删除消耗的计算机资源比线程多
  3. 进程空间独立,数据互不干扰,有专门通信方法;线程使用全局变量通信
  4. 一个进程可以有多个分支线程,两者有包含关系
  5. 多个线程共享进程资源,在共享资源操作时往往需要同步互斥处理
  6. 进程线程在系统中都有自己的特有属性标志,如ID,代码段,命令集等。

2. 使用场景

1. 任务场景 : 对于计算密集型程序(各种循环处理、计算等等),多进程效率高于多线程;对于IO密集型程序(文件处理、网络爬虫等涉及文件读写的操作),多线程可以提高效率。
如果是相对独立的任务模块,可能使用多进程,如果是多个分支共同形成一个整体任务可能用多线程。

2. 项目结构 : 多种编程语言实现不同任务模块,可能是多进程,或者前后端分离应该各自为一个进程。

3. 难易程度 : 通信难度,数据处理的复杂度来判断用进程间通信还是同步互斥方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值