Python实战笔记(三) 多线程 下

简介: Python实战笔记(三) 多线程

正文


4、维护线程安全


由于不同线程之间是并行的,如果多个线程同时修改一个数据,那么结果将会是不可预料的

import threading
import time
num = 0
def add(val):
    global num
    time.sleep(1)
    num += val
    print(num)
def main():
    for index in range(1, 9):
        worker = threading.Thread(target = add, args = (index,))
        worker.start()
if __name__ == '__main__':
    main()

程序每次运行的结果都是未知的,所以我们需要采取一些机制,使线程能够按照期望的方式工作


(1)锁对象:threading.Lock 与 threading.RLock


一个 threading.Lock 对象只有两种状态,锁定 (locked) 和非锁定 (unlocked)


任意一个线程可以使用 acquire() 方法将锁对象设置为锁定状态  (称为获得锁)


若此时有其它线程调用 acquire() 方法,那么该线程将会被阻塞


直至其它任意线程使用 release() 方法将锁对象设置为非锁定状态 (称为释放锁)


但如果在调用 release() 方法时,锁对象处于非锁定状态,则会抛出异常

锁对象状态 调用的方法 结果
unlocked acquire 将锁对象设置为锁定状态
locked acquire 阻塞当前线程
locked release 将锁对象设置为非锁定状态
unlocked release 抛出异常


threading.Lock 有两个常用的方法,分别是 acquire()release()


acquire(blocking = True, timeout = -1):获得锁


  • blocking:是否阻塞线程,默认为 True,表示没有获得锁时,将会阻塞当前线程
  • timeout  :最长阻塞时间,默认为 -1    ,表示一直阻塞下去,直至锁被释放


release():释放锁


import threading
import time
num = 0
lock = threading.Lock() # 声明锁对象
def add(val):
    lock.acquire() # 修改数据前,将锁对象设置为锁定状态
    global num
    time.sleep(1)
    num += val
    print(num)
    lock.release() # 修改数据后,将锁对象设置为非锁定状态
def main():
    for index in range(1, 8):
        worker = threading.Thread(target = add, args = (index,))
        worker.start()
if __name__ == '__main__':
    main()

threading.RLockthreading.Lock 的功能大致一样,但 threading.RLock 的特别之处在于:


  • 在同一线程内,多次调用 acquire() 方法,不会阻塞线程
  • 使用多少次 acquire() 方法获得锁,就必须使用多少次 release() 方法释放锁
  • 某线程通过 acquire() 方法获得锁,只允许该线程通过 release() 方法释放锁


(2)信号量对象:threading.Semaphore


一个 threading.Semaphore 对象在内部维护一个计数器,规定计数器的值不能小于 0


任意一个线程可以使用 acquire() 方法,使得计数器减 1


如果此时计数器已经为 0,那么将会阻塞当前线程,直至计数器大于 0


任意一个线程可以使用 release() 方法,使得计数器加 1

计数器 调用的方法 结果
大于 0 acquire 使计数器减 1
等于 0 acquire 阻塞当前线程
大于等于 0 release 使计数器加 1


threading.Semaphore 有两个常用的方法,分别是 acquire()release()


acquire(blocking = True, timeout = -1):使计数器减 1


  • blocking:是否阻塞线程,默认为 True,表示计数器为 0 时,将会阻塞当前线程
  • timeout  :最长阻塞时间,默认为 -1    ,表示一直阻塞下去 ,直至计数器大于 0


release():使计数器加 1


import threading
import time
num = 0
semaphore = threading.Semaphore(1) # 声明信号量,可以指定计数器初始值,默认为 1
def add(val):
    semaphore.acquire() # 使计数器减 1
    global num
    time.sleep(1)
    num += val
    print(num)
    semaphore.release() # 使计数器加 1
def main():
    for index in range(1, 8):
        worker = threading.Thread(target = add, args = (index,))
        worker.start()
if __name__ == '__main__':
    main()

使用信号量还可以使多个线程同时修改一个数据

import threading
import time
semaphore = threading.Semaphore(3)
def run():
    semaphore.acquire()
    time.sleep(1)
    print(threading.current_thread().name, 'Running')
    semaphore.release()
def main():
    for _ in range(7):
        worker = threading.Thread(target = run)
        worker.start()
if __name__ == '__main__':
    main()


(3)条件对象:threading.Condition


条件对象在锁对象的基础上封装而成,threading.Condition 常用的方法如下:


acquire():获得锁,调用底层(Lock 或 RLock)所对应的函数


release():释放锁,调用底层(Lock 或 RLock)所对应的函数


wait(timeout = None)

在调用该方法后,调用 release() 释放锁,然后阻塞当前线程,等待其它线程调用 notify() 唤醒

然后在被唤醒后,调用 acquire() 尝试获得锁

若有设置 timeout,即使没有其它线程调用 notify() 唤醒当前线程,也会在超时之后自动被唤醒


wait_for(predicate, timeout = None)

在调用该方法后,首先调用 predicate,若返回 True  ,则继续执行

若返回 False,调用 release() 释放锁,然后阻塞当前线程,等待其它线程调用 notify() 唤醒

然后在被唤醒后,也会调用 predicate,若返回 False,将会一直阻塞下去

若返回 True  ,调用 acquire() 尝试获得锁

若有设置 timeout,即使没有其它线程调用 notify() 唤醒当前线程,也会在超时之后自动被唤醒


notify(n = 1):唤醒 n 个线程

notify_all()  :唤醒所有线程


import threading
import time
data = 1
condition = threading.Condition()
def isEven():
    global data
    return data % 2 == 0
def wait():
    condition.acquire()        # 要先获得锁,才能释放锁
    print('wait_thread 进入等待')
    condition.wait_for(isEven) # 释放锁,阻塞当前线程,等待唤醒后重新获得锁,继续执行
    print('wait_thread 继续执行')
    condition.release()        # 重新获得锁后,记得要释放锁
def wake():
    global data
    condition.acquire() # 要先获得锁,再修改数据
    data = 2
    print('唤醒 wait_thread')
    condition.notify()
    condition.release() # 获得锁后,要释放锁
def main():
    wait_thread = threading.Thread(target = wait)
    wake_thread = threading.Thread(target = wake)
    wait_thread.start()
    time.sleep(1)
    wake_thread.start()
if __name__ == '__main__':
    main()
# 执行结果
# wait_thread 进入等待
# 唤醒 wait_thread
# wait_thread 继续执行


(4)事件对象:threading.Event


一个 threading.Event 对象在内部会维护一个标记,初始时默认为 False

threading.Event 常用的方法如下:


  • set()      :将标记设置为 True
  • clear()  :将标记设置为 False
  • wait()    :阻塞当前线程,直到标记变为 True
  • is_set():标记是否为 True
import threading
import time
event = threading.Event()
def wait():
    print(threading.current_thread().name, '进入等待')
    event.wait()
    print(threading.current_thread().name, '继续执行')
def wake():
    print('唤醒所有线程')
    event.set()
def main():
    for _ in range(5):
        wait_thread = threading.Thread(target = wait)
        wait_thread.start()
    time.sleep(1)
    wake_thread = threading.Thread(target = wake)
    wake_thread.start()
if __name__ == '__main__':
    main()
# 执行结果
# Thread-1 进入等待
# Thread-2 进入等待
# Thread-3 进入等待
# Thread-4 进入等待
# Thread-5 进入等待
# 唤醒所有线程
# Thread-1 继续执行
# Thread-2 继续执行
# Thread-5 继续执行
# Thread-4 继续执行
# Thread-3 继续执行

文章知识点与官方知识档案匹配,可进一步学习相关知识

目录
相关文章
|
19天前
|
SQL 关系型数据库 数据库
Python SQLAlchemy模块:从入门到实战的数据库操作指南
免费提供Python+PyCharm编程环境,结合SQLAlchemy ORM框架详解数据库开发。涵盖连接配置、模型定义、CRUD操作、事务控制及Alembic迁移工具,以电商订单系统为例,深入讲解高并发场景下的性能优化与最佳实践,助你高效构建数据驱动应用。
190 7
|
23天前
|
数据采集 Web App开发 数据安全/隐私保护
实战:Python爬虫如何模拟登录与维持会话状态
实战:Python爬虫如何模拟登录与维持会话状态
|
1月前
|
传感器 运维 前端开发
Python离群值检测实战:使用distfit库实现基于分布拟合的异常检测
本文解析异常(anomaly)与新颖性(novelty)检测的本质差异,结合distfit库演示基于概率密度拟合的单变量无监督异常检测方法,涵盖全局、上下文与集体离群值识别,助力构建高可解释性模型。
263 10
Python离群值检测实战:使用distfit库实现基于分布拟合的异常检测
|
1月前
|
存储 分布式计算 测试技术
Python学习之旅:从基础到实战第三章
总体来说,第三章是Python学习路程中的一个重要里程碑,它不仅加深了对基础概念的理解,还引入了更多高级特性,为后续的深入学习和实际应用打下坚实的基础。通过这一章的学习,读者应该能够更好地理解Python编程的核心概念,并准备好应对更复杂的编程挑战。
82 12
|
30天前
|
存储 数据采集 监控
Python文件操作全攻略:从基础到高级实战
本文系统讲解Python文件操作核心技巧,涵盖基础读写、指针控制、异常处理及大文件分块处理等实战场景。结合日志分析、CSV清洗等案例,助你高效掌握文本与二进制文件处理,提升程序健壮性与开发效率。(238字)
199 1
|
1月前
|
设计模式 消息中间件 安全
【JUC】(3)常见的设计模式概念分析与多把锁使用场景!!理解线程状态转换条件!带你深入JUC!!文章全程笔记干货!!
JUC专栏第三篇,带你继续深入JUC! 本篇文章涵盖内容:保护性暂停、生产者与消费者、Park&unPark、线程转换条件、多把锁情况分析、可重入锁、顺序控制 笔记共享!!文章全程干货!
116 1
|
1月前
|
存储 Java 调度
Python定时任务实战:APScheduler从入门到精通
APScheduler是Python强大的定时任务框架,通过触发器、执行器、任务存储和调度器四大组件,灵活实现各类周期性任务。支持内存、数据库、Redis等持久化存储,适用于Web集成、数据抓取、邮件发送等场景,解决传统sleep循环的诸多缺陷,助力构建稳定可靠的自动化系统。(238字)
350 1
|
20天前
|
Java 调度 数据库
Python threading模块:多线程编程的实战指南
本文深入讲解Python多线程编程,涵盖threading模块的核心用法:线程创建、生命周期、同步机制(锁、信号量、条件变量)、线程通信(队列)、守护线程与线程池应用。结合实战案例,如多线程下载器,帮助开发者提升程序并发性能,适用于I/O密集型任务处理。
178 0
|
28天前
|
机器学习/深度学习 监控 数据挖掘
Python 高效清理 Excel 空白行列:从原理到实战
本文介绍如何使用Python的openpyxl库自动清理Excel中的空白行列。通过代码实现高效识别并删除无数据的行与列,解决文件臃肿、读取错误等问题,提升数据处理效率与准确性,适用于各类批量Excel清理任务。
286 0
|
存储 监控 API
Python笔记2(函数参数、面向对象、装饰器、高级函数、捕获异常、dir)
Python笔记2(函数参数、面向对象、装饰器、高级函数、捕获异常、dir)
157 0

推荐镜像

更多