本文讨论了线程同步的概念 多线程 在Python编程语言中。
线程之间的同步
线程同步被定义为一种机制,它确保两个或多个并发线程不会同时执行某些特定的程序段,即 临界截面 .
关键部分是指程序中访问共享资源的部分。
例如,在下图中,3个线程试图同时访问共享资源或关键部分。
对共享资源的并发访问可能会导致 竞赛条件 .
当两个或多个线程可以访问共享数据并试图同时更改数据时,就会出现争用情况。因此,变量的值可能是不可预测的,并且取决于进程上下文切换的时间。
考虑下面的程序来理解竞速条件的概念:
import threading # global variable x x = 0 def increment(): """ function to increment global variable x """ global x x + = 1 def 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 = 175005 Iteration 1: x = 200000 Iteration 2: x = 200000 Iteration 3: x = 169432 Iteration 4: x = 153316 Iteration 5: x = 200000 Iteration 6: x = 167322 Iteration 7: x = 200000 Iteration 8: x = 169917 Iteration 9: x = 153589
在上述计划中:
- 两条线 t1 和 t2 都是在 主要任务 函数与全局变量 十、 设置为0。
- 每个线程都有一个目标函数 线程任务 在哪儿 定期的加薪 函数被调用100000次。
- 定期的加薪 函数将递增全局变量 十、 每次通话1次。
预期的最终价值 十、 是200000但我们在10次迭代中得到的 主要任务 函数是一些不同的值。
这是由于线程对共享变量的并发访问造成的 十、 .价值的不可预测性 十、 只是 竞赛条件 .
下面给出的图表显示了如何 竞赛条件 发生在上述程序中:
请注意 十、 在上图中是12,但由于比赛条件,结果是11! 因此,我们需要一个工具来在多个线程之间进行适当的同步。
使用锁
穿线 模块提供了一个 锁 上课来处理比赛情况。锁是使用 信号灯 操作系统提供的对象。
信号量是一个同步对象,它控制并行编程环境中多个进程/线程对公共资源的访问。它只是操作系统(或内核)存储中指定位置的一个值,每个进程/线程都可以检查并更改它。根据找到的值,进程/线程可以使用资源,或者会发现资源已经在使用,必须等待一段时间才能重试。信号量可以是二进制的(0或1),也可以有附加值。通常,使用信号量的进程/线程会检查该值,如果它使用资源,则会更改该值以反映这一点,以便后续的信号量用户知道等待。
锁 类提供以下方法:
- 获取([blocking]): 获得一把锁。锁可以是阻塞的,也可以是非阻塞的。
- 当使用设置为的阻塞参数调用时 符合事实的 (默认情况下),线程执行被阻止,直到锁被解锁,然后锁被设置为锁定并返回 符合事实的 .
- 当使用设置为的阻塞参数调用时 错误的 ,线程执行未被阻止。如果锁已解锁,则将其设置为锁定并返回 符合事实的 否则返回 错误的 立即
- 释放(): 解锁。
- 锁定后,将其重置为解锁,然后返回。如果任何其他线程在等待锁解锁时被阻塞,则只允许其中一个线程继续。
- 如果锁已解锁,则 线程错误 她长大了。
考虑下面给出的例子:
import threading # global variable x x = 0 def increment(): """ function to increment global variable x """ global x x + = 1 def 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 = 200000 Iteration 1: x = 200000 Iteration 2: x = 200000 Iteration 3: x = 200000 Iteration 4: x = 200000 Iteration 5: x = 200000 Iteration 6: x = 200000 Iteration 7: x = 200000 Iteration 8: x = 200000 Iteration 9: x = 200000
让我们试着一步一步地理解上面的代码:
- 首先,a 锁 对象是通过以下方式创建的:
lock = threading.Lock()
- 然后 锁 作为目标函数参数传递:
t1 = threading.Thread(target=thread_task, args=(lock,)) t2 = threading.Thread(target=thread_task, args=(lock,))
- 在目标函数的关键部分,我们使用 锁获得 方法一旦获得锁,其他线程就无法访问关键部分(此处, 定期的加薪 功能),直到使用 锁释放() 方法
lock.acquire() increment() lock.release()
正如您在结果中所看到的 十、 每次的结果是200000(这是预期的最终结果)。
下面给出的图表描述了上述程序中锁的实现:
至此,我们将结束本系列教程 Python中的多线程 . 最后,这里是多线程的一些优点和缺点:
优势:
- 它不会阻止用户。这是因为线程彼此独立。
- 由于线程并行执行任务,因此可以更好地使用系统资源。
- 增强了多处理器机器上的性能。
- 多线程服务器和交互式GUI专门使用多线程。
缺点:
- 随着线程数量的增加,复杂性也会增加。
- 共享资源(对象、数据)的同步是必要的。
- 调试很困难,结果有时不可预测。
- 导致饥饿的潜在死锁,即某些线程可能无法使用糟糕的设计
- 构造和同步线程是CPU/内存密集型的。
本文由 尼希尔·库马尔 .如果你喜欢GeekSforgek,并想贡献自己的力量,你也可以使用 贡献极客。组织 或者把你的文章寄到contribute@geeksforgeeks.org.看到你的文章出现在Geeksforgeks主页上,并帮助其他极客。
如果您发现任何不正确的地方,或者您想分享有关上述主题的更多信息,请写下评论。