Python 中的多线程设置 (同步)

在这里插入图片描述


本文将讲解 Python 编程语言中 多线程处理情况下线程同步的概念。

线程之间的同步

线程同步被定义为一种机制,它确保两个或多个并发线程不会同时执行某些称为关键段的特定程序段。

关键部分是指访问共享资源的程序部分。

例如,在下图中,3 个线程尝试同时访问共享资源或关键部分。

image.png

对共享资源的并发访问可能导致争用情况

当两个或多个线程可以访问共享数据并尝试同时更改共享数据时,就会发生争用情况。因此,变量的值可能是不可预测的,并且根据进程的上下文切换的时间而变化。

考虑下面的程序来理解争用条件的概念:

import threading

# 全局变量 x
x = 0


def increment():
    """用于递增全局变量x的函数"""
    global x
    x += 1


def thread_task():
    """ 线程的任务调用增量函数100000次。"""
    for _ in range(10000000):
        increment()


def main_task():
    global x
    # 将全局变量x设置为0
    x = 0

    # 创建线程
    t1 = threading.Thread(target=thread_task)
    t2 = threading.Thread(target=thread_task)

    # 开启线程
    t1.start()
    t2.start()

    # 等待线程完成
    t1.join()
    t2.join()


if __name__ == "__main__":
    for i in range(10):
        main_task()
        print("迭代 {0}: x = {1}".format(i, x))

运行结果:

image.png

在上面的程序中:

  • 在函数main_task中创建两个线程 t1t2,并且全局变量 x 设置为 0。
  • 每个线程都有一个目标函数thread_task其中增量函数被调用 100000 次。
  • increment 函数将在每次调用中将全局变量 x 递增 1。

x 的预期最终值为 200000,但我们在函数的 10 次迭代中得到main_task是一些不同的值。

(如果您每次的运行结果都一样,可能是由于您的计算机性能比较好,可以尝试这加大thread_task()方法的数据,如多加一个零或几个零,如10000000次)

发生这种情况是由于线程对共享变量 x 的并发访问。x 值的这种不可预测性只不过是竞态条件

下面给出的是一个图表,显示了在上面的程序中如何发生争用条件

image.png

请注意,上图中 x 的预期值为 12,但由于争用条件,结果是 11!

因此,我们需要一个工具来在多个线程之间进行适当的同步。

这里我们就会用到线程锁了

线程锁

线程模块提供了一个 Lock 类来处理争用条件。锁定是使用操作系统提供的信号量对象实现的。

信号量是一个同步对象,用于控制多个进程/线程对并行编程环境中公共资源的访问。它只是操作系统(或内核)存储中指定位置的值,每个进程/线程都可以检查该值,然后进行更改。根据找到的值,进程/线程可以使用该资源,或者会发现它已在使用中,并且必须等待一段时间才能重试。信号量可以是二进制(0 或 1),也可以具有其他值。通常,使用信号量的进程/线程会检查该值,然后,如果它使用资源,则更改该值以反映此值,以便后续信号量用户将知道等待。

Lock 类提供以下方法:

  • 获取([阻塞]) : 获取锁。锁可以是阻塞的,也可以是非阻塞的。

    • 当在将阻塞参数设置为 True(默认值)的情况下调用时,线程执行将被阻塞,直到锁定解锁,然后锁定设置为锁定并返回 True
    • 当在将阻塞参数设置为 False 的情况下调用时,不会阻塞线程执行。如果锁定已解锁,则将其设置为锁定并返回 True, 否则会立即返回 False
  • 释放() : 释放锁。

    • 锁定后,将其重置为已解锁,然后返回。如果任何其他线程被阻塞等待锁定解锁,请只允许其中一个线程继续。
    • 如果锁定已解锁,则会引发线程错误

请考虑下面给出的示例:

import threading

# 全局变量 x
x = 0


def increment():
    """用于递增全局变量x的函数"""
    global x
    x += 1


def thread_task(lock):
    """线程的任务调用增量函数100000次."""
    for _ in range(100000):
        lock.acquire()
        increment()
        lock.release()


def main_task():
    global x
    # 设置全局变量为 0
    x = 0

    # 创建线程锁
    lock = threading.Lock()

    # 创建线程
    t1 = threading.Thread(target=thread_task, args=(lock,))
    t2 = threading.Thread(target=thread_task, args=(lock,))

    # 开启线程
    t1.start()
    t2.start()

    # 等待所有线程完成
    t1.join()
    t2.join()


if __name__ == "__main__":
    for i in range(10):
        main_task()
        print("迭代 {0}: x = {1}".format(i, x))

运行结果:

image.png

让我们尝试一步一步地理解上面的代码:

  • 首先,使用以下命令创建 Lock 对象:

      lock = threading.Lock()
    
  • 然后,将 lock 作为目标函数参数传递:

      t1 = threading.Thread(target=thread_task, args=(lock,))
      t2 = threading.Thread(target=thread_task, args=(lock,))
    
  • 在目标函数的关键部分,我们使用 lock.acquire() 方法应用 lock。一旦获得锁,在使用 lock.release() 方法释放锁之前,没有其他线程可以访问关键部分(此处为增量函数)。

      lock.acquire()
      increment()
      lock.release()
    

    正如您在结果中看到的,x 的最终值每次都显示为 200000(这是预期的最终结果)。

下面给出了一个图表,描述了上述程序中锁的实现:

image.png

多线程处理的一些优点和缺点

最后,以下是多线程处理的一些优点和缺点:

优势:

  • 它不会阻止用户。这是因为线程彼此独立。
  • 由于线程并行执行任务,因此可以更好地利用系统资源。
  • 增强了多处理器计算机上的性能。
  • 多线程服务器和交互式 GUI 仅使用多线程处理。

弊:

  • 随着线程数量的增加,复杂性也会增加。
  • 共享资源(对象、数据)的同步是必要的。
  • 调试难度大,有时结果不可预测。
  • 导致饥饿的潜在死锁,即某些线程可能无法提供糟糕的设计
  • 构造和同步线程会占用大量 CPU/内存。
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Python多线程编程,为了避免资源竞争和数据不一致的问题,我们需要使用同步机制来保证线程之间的协调。以下是几种常用的同步机制: 1. Lock(锁):Lock 是最基本的同步机制之一,它可以确保在同一时间只有一个线程可以访问共享资源。 2. RLock(可重入锁):RLock 是 Lock 的升级版,它允许同一个线程多次获得锁,从而避免了死锁的问题。 3. Semaphore(信号量):Semaphore 是一种计数器,它用来控制对共享资源的访问数量。当计数器为 1 时,Semaphore 就相当于 Lock;当计数器大于 1 时,Semaphore 就可以允许多个线程同时访问共享资源。 4. Condition(条件变量):Condition 是一种高级的同步机制,它允许线程在某个条件满足时被唤醒。通常情况下,Condition 和 Lock 一起使用,来实现线程间的协调和通信。 5. Event(事件):Event 是一种简单的同步机制,它允许线程在某个事件发生时被唤醒。通常情况下,Event 被用来协调多个线程的启动和停止。 6. Barrier(屏障):Barrier 是一种同步机制,它可以让多个线程在某个点上进行同步,即所有线程必须同时到达该点才能继续执行。 以上是常见的同步机制,具体使用哪种机制则根据实际需求而定。在使用多线程编程时,需要注意线程之间的协调和通信,避免出现资源竞争和数据不一致的问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

佛系的老肖

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

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

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

打赏作者

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

抵扣说明:

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

余额充值