为什么在Python线程中使用global是错误的做法?

时间:2018-07-26 22:08:25

标签: python multithreading global

我在各个网站上都读过,使用global不好。我有一个应用程序,其中将300个对象存储在一个数组中。我想让8个线程贯穿这300个对象。这些对象的大小不同,例如介于10到50,000之间的整数,并且是随机分布的(请在此处考虑最坏的情况)。

基本上,我想启动8个线程,对一个对象进行处理,报告或存储结果,并拾取一个新对象300次。

我能想到的解决方案是设置一个全局锁和一个全局计数器,锁定数组,获取当前对象,增加计数器,释放锁。

有8个线程的1个锁。有1个8线程计数器。我有2个全局对象。我将结果存储在字典中,也可能是全局的,以使其对所有线程可见,但也对线程安全。我不喜欢做一些愚蠢的事情,例如子类化线程,并把300/8对象传递给每个对象,因为multiprocessing.pool为我做到了。那你会怎么做呢?另外,请说服我在这种情况下使用global是不好的。

1 个答案:

答案 0 :(得分:0)

将方法分类为“好”或“坏”有点简单-在实践中,如果设计对您有意义并完成了您设定要实现的目标,那么其他人(除了可能是您的老板之外)认为这是“好”的;它要么起作用,要么不起作用。另一方面,如果您的设计引起很多痛苦和折磨,那就表明您可能没有针对当前任务使用最合适的设计。

也就是说,有很多人认为全局变量存在问题是有正当理由的,尤其是与多线程结合使用时。

全局变量(带或不带多线程)的普遍问题是,随着程序的增大,从心理上跟踪程序的哪些部分可能正在读取和/或更新全局变量的值变得越来越困难。哪一次-因为它们是全局的,所以根据定义,程序的所有部分都可以访问它们,因此当您尝试遍历程序以找出是谁将全局变量设置为某些意外值时,该列表的嫌疑犯可能变得难以控制。 (对于小型程序来说,这并不是什么大问题,但是程序的规模越大,这个问题就越糟-而且,许多程序员通过痛苦的经验中学到,最好通过以下方式解决问题:首先尽可能避免全局变量,然后稍后必须重新编写大型,复杂的,有错误的程序)

在多线程程序的特定用例中,任何人都可以随时访问我的全局变量的属性变得更加危险,因为在多线程情况下,任何(只有通过适当的序列化(例如,通过在读取/写入共享数据之前锁定互斥体,然后对其进行解锁),才能安全地访问线程之间共享的非不变数据。理想情况下,程序员必须在不锁定互斥体的情况下永远不会意外地读取或写入任何共享的+可变数据-但是程序员是人为的,并且不可避免地会犯错误。如果有能力这样做,您(或其他人)迟早会忘记对特定全局变量的访问需要进行序列化,而只是继续进行读/写操作,然后您就可以会很痛苦,因为症状很少且是随机的,而且故障原因也不明显。

因此,聪明的程序员通常试图通过将对共享状态的访问限制为正确实现序列化的一组特定的,小的,精心编写的函数集(也称为API)来试图避免陷入此类陷阱这样就不需要其他代码了。这样做时,您要确保该特定API中的代码仅 有权访问共享数据,并且没有其他人可以这样做-这与全局变量是不可能的,例如根据定义,每个人都可以直接访问它。

人们不喜欢混合使用全局变量和多线程还有一个与性能相关的原因:序列化越多,程序利用多个CPU内核的能力就越少。特别是,如果8个线程中有7个线程大部分时间都被阻塞,等待互斥体可用,那么拥有8核CPU并不合适。

那么这与全局变量有什么关系?与此相关的是,在大多数情况下,很难或不可能证明一个全局变量永远不会被另一个线程访问,这意味着所有对该全局变量的访问都需要序列化。另一方面,对于非全局变量,您可以确保仅对单个线程提供对该变量的引用-此时,您已经有效地保证只有一个线程可以访问该变量(因为其他线程没有对此的引用,您知道它们无法访问它),并且由于有了保证,您不再需要序列化对该数据的访问,现在您的线程可以更高效地运行,因为它不必阻塞等待互斥锁。

(顺便说一句,CPython特别遭受由Python的Global Interpreter Lock引起的严重形式的隐式序列化的影响,这意味着即使是最好的多线程,受CPU约束的Python代码也不太可能使用超过一个CPU解决这个问题的唯一方法是改用多处理,或者使用较低级的语言(例如C)执行程序的大量计算,以便无需持有GIL就可以执行)