Python中的多线程指南|S2(同步)

本文讨论了线程同步的概念。多线程用Python编程语言编写。
线程之间的同步
线程同步被定义为一种机制, 可确保两个或多个并发线程不会同时执行某些特定的程序段, 即关键部分.

关键部分是指程序中访问共享资源的部分。
例如, 在下图中, 3个线程尝试同时访问共享资源或关键部分。
Python中的多线程指南|S2(同步)

文章图片
并发访问共享资源可能导致比赛条件.
当两个或多个线程可以访问共享数据并且它们试图同时更改它们时, 就会发生竞争状态。结果, 变量的值可能是不可预测的, 并且取决于过程的上下文切换的时间而变化。
考虑下面的程序以了解竞争条件的概念:
import threading# global variable x x = 0def increment(): """ function to increment global variable x """ global x x + = 1def thread_task(): """ task for thread calls increment function 100000 times. """ for _ in range ( 100000 ): increment()def main_task(): global x # setting global variable x as 0 x = 0# creating threads t1 = threading.Thread(target = thread_task) t2 = threading.Thread(target = thread_task)# start threads t1.start() t2.start()# wait until threads finish their job t1.join() t2.join()if __name__ = = "__main__" : for i in range ( 10 ): main_task() print ( "Iteration {0}: x = {1}" . format (i, x))

输出如下:
Iteration 0: x = 175005Iteration 1: x = 200000Iteration 2: x = 200000Iteration 3: x = 169432Iteration 4: x = 153316Iteration 5: x = 200000Iteration 6: x = 167322Iteration 7: x = 200000Iteration 8: x = 169917Iteration 9: x = 153589

在上面的程序中:
  • 两个线程t1和t2创建于主要任务函数和全局变量X设置为0。
  • 每个线程都有一个目标函数thread_task在其中增量函数被称为100000次。
  • 增量函数将增加全局变量X在每个通话中减1。
的预期最终价值X是200000但我们在10次迭代中得到的主要任务函数是一些不同的值。
这是由于线程同时访问共享变量而发生的X。价值的这种不可预测性X只不过是比赛条件.
下图显示了如何
比赛条件
发生在以上程序中:
Python中的多线程指南|S2(同步)

文章图片
请注意,
X
上图中的是12, 但由于竞赛条件, 结果是11!
因此, 我们需要一个工具来在多个线程之间进行适当的同步。
使用锁
穿线模块提供了锁上课以应对比赛条件。锁是使用信号操作系统提供的对象。
信号量是一个同步对象, 它控制并行编程环境中多个进程/线程对公共资源的访问。它只是操作系统(或内核)存储中指定位置的值, 每个进程/线程都可以检查然后更改。取决于找到的值, 进程/线程可以使用该资源, 或者将发现该资源已在使用中, 并且必须等待一段时间才能重试。信号量可以是二进制的(0或1), 也可以具有其他值。通常, 使用信号量的进程/线程会检查该值, 然后(如果它使用了资源)则更改该值以反映该值, 以便后续的信号量用户将知道等待。
锁该类提供以下方法:
  • 获取([阻止]):获取锁。锁可以是锁定的也可以是非锁定的。
    • 在将阻塞参数设置为的情况下调用true(默认设置), 直到锁解锁后, 线程执行才会被阻止, 然后将锁设置为已锁定并返回true.
    • 在将阻塞参数设置为的情况下调用false, 不会阻止线程执行。如果锁定已解锁, 则将其设置为锁定并返回true否则返回false立即。
  • 发布() :释放锁。
    • 锁锁定后, 将其重置为解锁状态, 然后返回。如果其他任何线程被阻塞, 等待锁解锁, 则允许其中一个线程继续进行。
    • 如果锁已解锁, 则线程错误被提出。
考虑下面给出的示例:
import threading# global variable x x = 0def increment(): """ function to increment global variable x """ global x x + = 1def thread_task(lock): """ task for thread calls increment function 100000 times. """ for _ in range ( 100000 ): lock.acquire() increment() lock.release()def main_task(): global x # setting global variable x as 0 x = 0# creating a lock lock = threading.Lock()# creating threads t1 = threading.Thread(target = thread_task, args = (lock, )) t2 = threading.Thread(target = thread_task, args = (lock, ))# start threads t1.start() t2.start()# wait until threads finish their job t1.join() t2.join()if __name__ = = "__main__" : for i in range ( 10 ): main_task() print ( "Iteration {0}: x = {1}" . format (i, x))

输出如下:
Iteration 0: x = 200000Iteration 1: x = 200000Iteration 2: x = 200000Iteration 3: x = 200000Iteration 4: x = 200000Iteration 5: x = 200000Iteration 6: x = 200000Iteration 7: x = 200000Iteration 8: x = 200000Iteration 9: x = 200000

让我们尝试逐步理解上面的代码:
首先,

使用以下对象创建对象:
lock = threading.Lock()

然后,

【Python中的多线程指南|S2(同步)】作为目标函数参数传递:
t1 = threading.Thread(target=thread_task, args=(lock, ))t2 = threading.Thread(target=thread_task, args=(lock, ))

在目标函数的关键部分, 我们使用
lock.acquire()
方法。一旦获得了锁, 其他任何线程都无法访问关键部分(此处,
增量
功能), 直到使用释放锁为止
lock.release()
方法。
lock.acquire()increment()lock.release()

正如你在结果中看到的那样, X每次得出200000(这是预期的最终结果)。
这是下面给出的示意图, 描述了上述程序中锁的实现:
Python中的多线程指南|S2(同步)

文章图片
这使我们到本教程系列的结尾
Python中的多线程
.
最后, 这里是多线程的一些优点和缺点:
优点:
  • 它不会阻止用户。这是因为线程彼此独立。
  • 由于线程并行执行任务, 因此可以更好地利用系统资源。
  • 增强了多处理器计算机上的性能。
  • 多线程服务器和交互式GUI专门使用多线程。
缺点:
  • 随着线程数量的增加, 复杂性也随之增加。
  • 共享资源(对象, 数据)的同步是必要的。
  • 调试困难, 结果有时无法预测。
  • 可能导致饥饿的潜在死锁, 即某些线程可能设计不佳
  • 构造和同步线程需要占用大量CPU /内存。
如果发现任何不正确的地方, 或者想分享有关上述主题的更多信息, 请写评论。
注意怪胎!巩固你的基础Python编程基础课程和学习基础知识。
首先, 你的面试准备可通过以下方式增强你的数据结构概念:Python DS课程。

    推荐阅读