简体   繁体   English

在某些情况下,Python线程可以安全地操作共享状态吗?

[英]Are there some cases where Python threads can safely manipulate shared state?

Some discussion in another question has encouraged me to to better understand cases where locking is required in multithreaded Python programs. 另一个问题中的一些讨论鼓励我更好地理解多线程Python程序中需要锁定的情况。

Per this article on threading in Python, I have several solid, testable examples of pitfalls that can occur when multiple threads access shared state. 文章在Python线程,我有当多个线程同时访问共享的状态可能出现的缺陷的几个实体,可测试的例子。 The example race condition provided on this page involves races between threads reading and manipulating a shared variable stored in a dictionary. 此页面上提供的示例竞争条件涉及读取和操作存储在字典中的共享变量的线程之间的竞争。 I think the case for a race here is very obvious, and fortunately is eminently testable. 我认为这场比赛的情况非常明显,幸运的是,这是非常值得考验的。

However, I have been unable to evoke a race condition with atomic operations such as list appends or variable increments. 但是,我无法通过列表追加或变量增量等原子操作来唤起竞争条件。 This test exhaustively attempts to demonstrate such a race: 这个测试详尽地试图展示这样一个种族:

from threading import Thread, Lock
import operator

def contains_all_ints(l, n):
    l.sort()
    for i in xrange(0, n):
        if l[i] != i:
            return False
    return True

def test(ntests):
    results = []
    threads = []
    def lockless_append(i):
        results.append(i)
    for i in xrange(0, ntests):
        threads.append(Thread(target=lockless_append, args=(i,)))
        threads[i].start()
    for i in xrange(0, ntests):
        threads[i].join()
    if len(results) != ntests or not contains_all_ints(results, ntests):
        return False
    else:
        return True

for i in range(0,100):
    if test(100000):
        print "OK", i
    else:
        print "appending to a list without locks *is* unsafe"
        exit()

I have run the test above without failure (100x 100k multithreaded appends). 我已经运行了上面的测试而没有失败(100x 100k多线程附加)。 Can anyone get it to fail? 任何人都可以让它失败吗? Is there another class of object which can be made to misbehave via atomic, incremental, modification by threads? 是否有另一类对象可以通过线程的原子,增量和修改来行为不端?

Do these implicitly 'atomic' semantics apply to other operations in Python? 这些隐式“原子”语义是否适用于Python中的其他操作? Is this directly related to the GIL? 这与GIL直接相关吗?

Appending to a list is thread-safe, yes. 附加到列表是线程安全的,是的。 You can only append to a list while holding the GIL, and the list takes care not to release the GIL during the append operation (which is, after all, a fairly simple operation.) The order in which different thread's append operations go through is of course up for grabs, but they will all be strictly serialized operations because the GIL is never released during an append. 您只能在持有GIL的同时附加到列表,并且列表在append操作期间注意不要释放GIL(毕竟,这是一个相当简单的操作。)不同线程的追加操作所经过的顺序是当然是为了争夺,但它们都将是严格的序列化操作,因为GIL在附加期间永远不会被释放。

The same is not necessarily true for other operations. 其他操作也不一定如此。 Lots of operations in Python can cause arbitrary Python code to be executed, which in turn can cause the GIL to be released. Python中的大量操作可能会导致执行任意Python代码,从而导致GIL被释放。 For example, i += 1 is three distinct operations, "get i ', "add 1 to it" and "store it in i ". "add 1 to it" would translate (in this case) into it.__iadd__(1) , which can go off and do whatever it likes. 例如, i += 1是三个不同的操作,“得到i ”,“向它添加1”并“将它存储在i ”中。“向它添加1”将(在这种情况下)转换为it.__iadd__(1) ,可以去做任何它喜欢的事情。

Python objects themselves guard their own internal state -- dicts won't get corrupted by two different threads trying to set items in them. Python对象本身保护自己的内部状态 - dicts不会被试图在其中设置项目的两个不同线程破坏。 But if the data in the dict is supposed to be internally consistent, neither the dict nor the GIL does anything to protect that, except (in usual thread fashion) by making it less likely but still possible things end up different than you thought. 但是如果dict中的数据应该是内部一致的,那么dict和GIL都没有做任何事情来保护它,除了(以通常的线程方式)使它不太可能,但仍然可能的事情最终会与你想象的不同。

In CPython, thread switching is done when sys.getcheckinteval() bycodes have been executed. 在CPython中,在执行sys.getcheckinteval()bycodes时完成线程切换。 So a context switch can never occur during the execution of a single bytecode, and operations that are encoded as a single bytecode are inherently atomic and threadsafe, unless that bytecode executes other Python code or calls C code that releases the GIL. 因此,在执行单个字节码期间永远不会发生上下文切换,并且编码为单个字节码的操作本身就是原子和线程安全的,除非该字节码执行其他Python代码或调用释放GIL的C代码。 Most operations on the built-in collection types (dict, list etc) fall into the 'inherently threadsafe' category. 内置集合类型(dict,list等)上的大多数操作属于“固有线程安全”类别。

However this is an implementation detail that is specific to the C implementation of Python, and should not be relied upon. 但是,这是一个特定于Python的C实现的实现细节,不应该依赖它。 Other versions of Python (Jython, IronPython, PyPy etc) may not behave in the same way. 其他版本的Python(Jython,IronPython,PyPy等)可能不会以相同的方式运行。 There is also no guarantee that future versions of CPython will keep this behaviour. 也不能保证CPython的未来版本会保持这种行为。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM