你为什么要锁定线程?

时间:2011-06-18 00:27:58

标签: python multithreading locking thread-safety

我已经阅读了很多关于锁定线程的例子..但为什么要锁定它们呢? 根据我的理解,当您启动线程而不加入它们时,它们将与主线程和所有其他线程竞争资源,然后执行,有时同时执行,有时不执行。

锁定是否确保线程不会同时执行?

另外,线程同时执行有什么问题?那不是更好吗? (更快的整体执行)

当您锁定线程时,它会锁定它们还是您可以选择要锁定的线程? (无论锁定实际上是什么......)

我指的是使用锁定函数(如lock())并在线程模块中获取btw ......

2 个答案:

答案 0 :(得分:13)

锁允许您强制多个线程一次访问一个资源,而不是所有线程同时尝试访问资源。

正如您所注意到的,通常您确实希望线程同时执行。但是,假设您有两个线程,它们都写入同一个文件。如果他们试图同时写入同一个文件,他们的输出将会混合在一起,并且这两个线程都不会真正成功地将文件放入它想要的内容。

现在也许这个问题不会一直出现。大多数情况下,线程不会尝试一次写入文件。但有时候,也许有一次在一千次跑步中,他们就会这样做。所以也许你有一个看似随机发生的bug,很难重现,因此难以修复。啊!

或者也许......这发生在我工作的公司......你有这样的错误,但不知道他们在那里,因为如果你的电脑只有几个CPU,它们非常罕见,几乎没有你的任何客户都有超过4个。然后他们都开始购买16个CPU盒子......你的软件运行的线程与CPU内核一样多,所以你突然崩溃或得到错误的结果。

所以无论如何,回到文件。为了防止线程相互踩踏,每个线程必须在写入之前获取文件锁定。一次只有一个线程可以保持锁定,因此一次只能有一个线程写入该文件。线程保持锁定,直到完成写入文件,然后释放锁定,以便另一个线程可以使用该文件。

如果线程正在写入不同的文件,则不会出现此问题。这是一个解决方案:让您的线程写入不同的文件,并在必要时将它们合并。但这并不总是可行的;有时候,只有一件事。

它不一定是文件。假设您试图简单地计算一堆不同文件中字母“A”的出现次数,每个文件一个线程。你认为,很明显,每次看到“A”时,我都会让所有线程增加相同的内存位置。但!当你去增加保持计数的变量时,计算机将变量读入寄存器,递增寄存器,然后将值存回。如果两个线程同时读取该值,同时递增该值并将其同时存储,该怎么办?他们都从10开始,将它增加到11,然后再存储11。因此,当计数器应为12时,计数器11应该是:你丢失了一个计数。

获取锁定可能很昂贵,因为您必须等到其他任何人正在使用该资源。这就是Python的Global Interpreter Lock是性能瓶颈的原因。因此,您可能决定完全避免使用共享资源。每个线程都保留自己的计数,而不是使用单个内存位置来保存文件中的“A”数,并且最后将它们全部添加(类似于我建议的文件解决方案,有趣的是)

答案 1 :(得分:9)

首先,锁是为保护资源而设计的;线程没有“锁定”或“解锁”它们/获取/锁定(在资源上)和/ release / a lock(在资源上)。

你想让线程尽可能同时运行是正确的,但让我们来看看:

y=10

def doStuff( x ):
    global y
    a = 2 * y
    b = y / 5
    y = a + b + x
    print y

t1 = threading.Thread( target=doStuff, args=(8,) )
t2 = threading.Thread( target=doStuff, args=(8,) )
t1.start()
t2.start()
t1.join()
t2.join()

现在,您可能知道其中任何一个线程都可以完成并首先打印。你会期望看到两个输出30。

但他们可能不会。

y是一个共享资源,在这种情况下,读取和写入y的位是所谓的“临界区”的一部分,应该由锁保护。原因是你没有得到工作单元:任何一个线程都可以随时获得CPU。

这样想:

t1很高兴执行代码,然后点击

a = 2 * y

现在t1有一个= 20并且暂停执行一段时间。当t1等待更多CPU时间时,t2变为活动状态。 t2执行:

a = 2 * y
b = y / 5
y = a + b + x

此时全局变量y = 30

t2停止一点停止,t1再次启动。它执行:

b = y / 5
y = a + b + x

由于设定b时y为30,因此b = 6,y现在设定为34。

印刷品的顺序也是不确定的,你可能先得到30或者先得到34。

使用锁定我们会:

global l
l = threading.Lock()
def doStuff( x ):
    global y
    global l
    l.acquire()
    a = 2 * y
    b = y / 5
    y = a + b + x
    print y
    l.release()

这必然使这段代码成为线性 - 一次只有一个线程。但是如果整个程序是顺序的,那么你不应该使用线程。我们的想法是,您可以根据可以执行外部锁定并且并行运行的代码百分比来提高速度。这是(一个原因)为什么在2核系统上使用线程不会使所有内容的性能加倍。

锁本身也是一个共享资源,但它必须是:一旦一个线程获得锁,所有其他尝试获取/ same / lock的线程都会阻塞,直到它被释放。一旦它被释放,第一个向前移动并获得锁定的线程将阻止所有其他等待的线程。

希望这已经足够了!