Python并发编程之threading模块
admin
2024-02-04 03:38:09
0

threading 模块

threading模块提供Thread类和各种同步原语,用于编写多线程的程序。

Thread类用于表示单独的控制线程。使用下面的函数可以创建一个新的线程。

Thread(group=None, target=None, name=None, args=(), kwargs={})

此函数创建一个新的Thread实例。

  • group的值是none,为以后的版本而保留。
  • target是一个可调用对象,线程启动时,run()方法将调用此对象,它的默认值是none,表示不调用任何内容。
  • name是线程名称。默认键创建一个"Tread-N"格式的唯一名称。
  • args是传递给target函数的参数元组。
  • kwargs是传递个target的关键字参数的字典

Thread的实例t支持以下方法和属性。

  • t.start()
  • t.run()
  • t.join([timeout])
  • t.is_alive()
  • t.name()
  • t.ident
  • t.daemon

下面这个例子说明如何以线程的形式创建和启动一个函数。

import threading
import timedef clock(interval):while True:print("the time is %s" % time.ctime())time.sleep(interval)t = threading.Thread(target=clock,args=(5, ))
t.daemon = True
t.start()

下面这个例子说明如何将同一个线程定义为一个类:

class ClockThread(threading.Thread):def __init__(self, interval):threading.Thread.__init__(self)self.daemon = Trueself.interval = intervaldef run(self):while True:print("the time is %s" % time.ctime())time.sleep(self.interval)t = ClockThread(5)
t.start()

如果将线程定义为类,并且定义自己的__init__()方法,必须像上面这样调用基类构造函数Thread.init(), 如果忘记这一点,将导致严重错误。除了run()方法外,改写线程已经定义好的其他方法也会出现错误。

以上例子中,对于daemon属性的设置,是永远在后台运行的线程的常见功能。通常python在解释器退出之前,会等待所有线程终止。设置daemon标志会使解释器在主程序退出后立即退出。在这种情况下,daemonic线程将被销毁。


Timer对象

Timer对象用于在稍后的某个时间执行一个函数。

Timer(interval, func [, args [, kwargs]])

创建定时器对象,在过去interval秒时间之后运行函数func。 args和kwargs是传递给func的参数和关键字参数。在调用start()方法后悔启动定时器。

Timer对象实例t具有以下方法:

  • t.start()
  • t.cancel()

Lock对象

原语锁定(或互斥锁定)是一个同步原语,状态是“已锁定”或“未锁定”之一。两个方法acquire()和release()用于修改锁定的状态。如果状态为锁定,尝试获取锁定就被阻塞,直到锁定被释放为止。如果有多个线程等待获取锁定,当锁定被释放时,只有一个线程能够得到它。等待线程获得锁定的顺序是未定义的。

使用下面的构造函数创建新的Lock对象, 初始状态为“未锁定”

Lock()

Lock对象的实例lock支持以下方法:

  • lock.acquire([blocking])
  • lock.release()

RLock

可重入锁定是一个类似于Lock对象的同步原语,但同一个线程可以多次获取它。这允许拥有锁定的线程执行嵌套的acquire()和release()操作。在这种情况下,只有最外面的release()操作才能将锁定重置为未锁定状态.

使用下面的构造函数可以创建一个新的RLock对象

RLock()

RLock的实例rlock支持以下方法:

  • rlock.acquire([blocking])
  • rlock.release()

信号量和有边界的信号量

信号量是一个机遇计数器的同步原语,每次调用acquire()方法时此计数器的值减1, 每次调用release()时此计数器的值加1.如果计数器为0,acquire()方法将会阻塞,知道其他线程调用release()方法为止。

Semaphore([value])

创建一个新的信号量。value是计数器的初始值。如果省略此参数,计数器将被置为1.

Semaphore的实例s支持以下方法:

  • s.acquire([blocking])
  • s.release()
BoundedSemaphore([value])

创建一个新的信号机。value是计数器的初始值。如果省略此参数,计数器的值将被置为1.
BoundedSemaphore的工作方式和Semaphore完全相同,但release()操作的次数不能超过acquire()操作的次数。

信号机和互斥锁之间的微妙差别在于:信号机可用于发射信号。例如,可以从不同线程调用acquire()和release()方法,以便在生产者和消费者线程之间进行通信。


事件

事件用于在线程之间通信。一个线程发出“事件”信号,一个或多个其他线程等待它。Event实例管理着一个内部标志,可以使用set()方法将它置为True, 或者使用clear()方法将它重置为false。wait()方法将阻塞直到标志位true。

Event()

创建新的Event实例,并将内部标志置为false。Event实例e具有以下方法:

  • e.is_set()
  • e.set()
  • e.clear()
  • e.wait([timeout])

尽管Event对象可用于给其他线程发信号,但不应该使用它们来实现在生产者/消费者问题中十分典型的通知。例如,应该避免写出下面这样的代码:

evt = Event()def producer():while True:# 生产项目...evt.signal()def consumer():while True:# 等待一个项目evt.wait()# 使用项目...# 清除事件并再次等待evt.clear()

这段代码并不可靠,因为在evt.wait()和evt.clear()之间,生产者可能生产了一个新的项目。但是通过清除事件,在生产者创建一个新项目之前,使用者可能看不到这个新的项目。最好的情况是,程序将经过一段很短的停滞,对项目的处理被莫名其妙推迟了。最坏的情况是,由于事件信号丢失,整个程序将会挂起。要解决这类问题,最好使用条件变量。


条件变量

条件变量是构建在另一个锁定上的同步原语。但需要线程关注特定的状态变化或事件的发生时将使用这个锁定。典型的用法是生产者/消费者问题,其中一个线程生产的数据供另一个线程使用。

使用下面的构造函数可以创建一个新的Condition实例:

Condition([lock])

创建新的条件变量。lock是可选的Lock或者RLock实例。如果未提供Lock参数,就会创建新的RLock实例供条件变量使用。

条件变量cv支持以下方法:

  • cv.acquire(*args)
  • cv.release()
  • cv.wait([timeout])
  • cv.notify([n])
  • cv.notify_all()

下面这个例子提供了使用条件变量的模板:

cv = threading.Condition()
def producer():while True:cv.acquire()produce_item()cv.notify()cv.release()def consumer():while True:cv.acquire()while not item_is_available():cv.wait()   # 等待项目出现cv.release()consume_item()

使用条件变量时需要注意的是,如果存在多个线程等待同一个条件,notify()操作可能唤醒它们中的一个或多个。因此,始终有这样的可能:某个线程被唤醒后,却发现它等待的条件不存在了。这解释了在consumer()函数中使用while循环的原因。如果线程醒来,但是生产的项目已经消失,它就会回去等待下一个信号。


使用Lock

使用诸如Lock、RLock或Semaphore之类的锁定原语时,必须多加小心。锁定的错误管理经常导致死锁或竞争条件。依赖锁定的代码应该保证当出现异常时正确地释放锁定。典型的代码如下:

try:lock.acquire()# 关键部分statements...
finally:lock.release()

另外,所有锁定还支持上下文管理协议(一个小小的清理器):

with lock:# 关键部分statements...

在上面例子中,with语句自动获取锁定,并且在控制流离开上下文时自动释放锁定。

此外,编写代码时一般应该避免同时获取多个锁定,例如:

with lock_A:# 关键部分statements...with lock_B:# B的关键部分statements...

这种写法通常很容易导致应用程序神秘死锁。尽管有几种策略可以避免出现这种情况(例如分层锁定),但最好在编写代码时就避免这种写法。


线程终止与挂起

线程没有任何方法可用于强制终止或挂起。这是设计上的原因,因为编写线程程序本身就十分复杂。例如,如果某个线程已经获取了锁定,在它能够释放锁定之前强制终止或挂起它,将导致整个应用程序出现死锁。此外,终止时一般不能简单地“释放锁定”, 因为复杂的线程同步经常涉及锁定和解除锁定操作,而这些操作在执行时的次序是要十分精确的。

如果腰围终止或挂起提供支持,需要自己构建这些功能。一般的做法是在循环中运行线程,这个循环的作用是定期检查线程状态以决定它是否应该终止


实用工具函数

  • active_count()
  • current_thread()
  • enumerate()
  • local()
  • setprofile(func)
  • settrace(func)
  • stack_size([size])

全局解释器锁定 GIL(Global Interpreter Lock)

python解释器被一个锁定保护,该锁定只允许一次执行一个线程,即便存在多个可用的处理器。

在计算密集型程序中,这严重限制了线程的作用, 事实上,在计算密集型应用程序中使用线程,经常比仅仅按照顺序执行同样的工作慢的多。因此,实际上应该只在主要关注I/O的程序,如网络服务器中使用线程。对于计算密度更高的任务,最好使用C扩展模块或multiprocessing模块来代替。C扩展具有释放解释器锁定和并行运行的选项,可以做到当释放锁定时不与解释器进行交互。multiprocessing模块将工作分派给不受锁定限制的单独子进程。


使用线程编程

尽管在python中可以使用各种锁定和同步原语的组合编写非常传统的多线程程序,但有一种首推的编程方式要优于其他所有编程方法,即将多线程程序组织为多个独立任务的集合,在这些任务之间通过消息队列进行通信。

相关内容

热门资讯

【MySQL】锁 锁 文章目录锁全局锁表级锁表锁元数据锁(MDL)意向锁AUTO-INC锁...
【内网安全】 隧道搭建穿透上线... 文章目录内网穿透-Ngrok-入门-上线1、服务端配置:2、客户端连接服务端ÿ...
GCN的几种模型复现笔记 引言 本篇笔记紧接上文,主要是上一篇看写了快2w字,再去接入代码感觉有点...
数据分页展示逻辑 import java.util.Arrays;import java.util.List;impo...
Redis为什么选择单线程?R... 目录专栏导读一、Redis版本迭代二、Redis4.0之前为什么一直采用单线程?三、R...
【已解决】ERROR: Cou... 正确指令: pip install pyyaml
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
Lock 接口解读 前置知识点Synchronized synchronized 是 Java 中的关键字,...
Win7 专业版安装中文包、汉... 参考资料:http://www.metsky.com/archives/350.htm...
3 ROS1通讯编程提高(1) 3 ROS1通讯编程提高3.1 使用VS Code编译ROS13.1.1 VS Code的安装和配置...
大模型未来趋势 大模型是人工智能领域的重要发展趋势之一,未来有着广阔的应用前景和发展空间。以下是大模型未来的趋势和展...
python实战应用讲解-【n... 目录 如何在Python中计算残余的平方和 方法1:使用其Base公式 方法2:使用statsmod...
学习u-boot 需要了解的m... 一、常用函数 1. origin 函数 origin 函数的返回值就是变量来源。使用格式如下...
常用python爬虫库介绍与简... 通用 urllib -网络库(stdlib)。 requests -网络库。 grab – 网络库&...
药品批准文号查询|药融云-中国... 药品批文是国家食品药品监督管理局(NMPA)对药品的审评和批准的证明文件...
【2023-03-22】SRS... 【2023-03-22】SRS推流搭配FFmpeg实现目标检测 说明: 外侧测试使用SRS播放器测...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
初级算法-哈希表 主要记录算法和数据结构学习笔记,新的一年更上一层楼! 初级算法-哈希表...
进程间通信【Linux】 1. 进程间通信 1.1 什么是进程间通信 在 Linux 系统中,进程间通信...
【Docker】P3 Dock... Docker数据卷、宿主机与挂载数据卷的概念及作用挂载宿主机配置数据卷挂载操作示例一个容器挂载多个目...