Python并发编程之threading模块

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 time

def 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 = True
        self.interval = interval
        
    def 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线程将被销毁。


1. Timer对象

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

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

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

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

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

2. Lock对象

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

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

Lock()

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

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

3. RLock

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

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

RLock()

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

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

4. 信号量和有边界的信号量

信号量是一个机遇计数器的同步原语,每次调用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()方法,以便在生产者和消费者线程之间进行通信。


5. 事件

事件用于在线程之间通信。一个线程发出“事件”信号,一个或多个其他线程等待它。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()之间,生产者可能生产了一个新的项目。但是通过清除事件,在生产者创建一个新项目之前,使用者可能看不到这个新的项目。最好的情况是,程序将经过一段很短的停滞,对项目的处理被莫名其妙推迟了。最坏的情况是,由于事件信号丢失,整个程序将会挂起。要解决这类问题,最好使用条件变量。


6. 条件变量

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

使用下面的构造函数可以创建一个新的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循环的原因。如果线程醒来,但是生产的项目已经消失,它就会回去等待下一个信号。


7. 使用Lock

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

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

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

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

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

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

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

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


8. 线程终止与挂起

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

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


9. 实用工具函数

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

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

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

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


11. 使用线程编程

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

  • 2
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Python并发编程是指使用Python编写并行执行的程序。它允许多个任务同时执行,提高程序的效率。在Python中,有多种方法可以实现并发编程,其中包括多线程、多进程和异步编程。多线程是指在一个程序中同时运行多个线程,每个线程执行一个任务。多进程是指在一个程序中同时运行多个进程,每个进程执行一个任务。异步编程是指通过回调函数或协程来实现并发执行任务的方式。这些方法可以根据需求选择合适的方式来进行并发编程并提高程序的性能。其中,学习Python并发编程推荐在Linux上学习,因为Linux系统对于进程之间的通信有较好的支持,而Mac系统的核心也是Linux,所以Linux上的任何Python代码在Mac上都是可行的。官方文档提供了有关Python并发编程的详细信息,可以在https://docs.python.org/3/library/multiprocessing.html进行查阅。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Python笔记.docx](https://download.csdn.net/download/bxy0806/88235414)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [深入浅出讲解Python并发编程](https://blog.csdn.net/wanger5354/article/details/122016057)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [python并发编程](https://blog.csdn.net/weixin_43915382/article/details/122003007)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

smart_cat

你的鼓励将是我写作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值