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

一、线程

线程的基本概念

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、付费专栏及课程。

余额充值