Python中的多线程|集2(同步)

本文讨论了线程同步的概念 多线程 在Python编程语言中。

null

线程之间的同步

线程同步被定义为一种机制,它确保两个或多个并发线程不会同时执行某些特定的程序段,即 临界截面 .

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

例如,在下图中,3个线程试图同时访问共享资源或关键部分。 图片[1]-Python中的多线程|集2(同步)-yiteyi-C++库

对共享资源的并发访问可能会导致 竞赛条件 .

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

考虑下面的程序来理解竞速条件的概念:

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次迭代中得到的 主要任务 函数是一些不同的值。

这是由于线程对共享变量的并发访问造成的 十、 .价值的不可预测性 十、 只是 竞赛条件 .

下面给出的图表显示了如何 竞赛条件 发生在上述程序中: 图片[2]-Python中的多线程|集2(同步)-yiteyi-C++库

请注意 十、 在上图中是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(这是预期的最终结果)。

下面给出的图表描述了上述程序中锁的实现: 图片[3]-Python中的多线程|集2(同步)-yiteyi-C++库

至此,我们将结束本系列教程 Python中的多线程 . 最后,这里是多线程的一些优点和缺点:

优势:

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

缺点:

  • 随着线程数量的增加,复杂性也会增加。
  • 共享资源(对象、数据)的同步是必要的。
  • 调试很困难,结果有时不可预测。
  • 导致饥饿的潜在死锁,即某些线程可能无法使用糟糕的设计
  • 构造和同步线程是CPU/内存密集型的。

本文由 尼希尔·库马尔 .如果你喜欢GeekSforgek,并想贡献自己的力量,你也可以使用 贡献极客。组织 或者把你的文章寄到contribute@geeksforgeeks.org.看到你的文章出现在Geeksforgeks主页上,并帮助其他极客。

如果您发现任何不正确的地方,或者您想分享有关上述主题的更多信息,请写下评论。

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享