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

Eri*_*son 5 python multithreading gil

另一个问题中的一些讨论鼓励我更好地理解多线程Python程序中需要锁定的情况.

文章在Python线程,我有当多个线程同时访问共享的状态可能出现的缺陷的几个实体,可测试的例子.此页面上提供的示例竞争条件涉及读取和操作存储在字典中的共享变量的线程之间的竞争.我认为这场比赛的情况非常明显,幸运的是,这是非常值得考验的.

但是,我无法通过列表追加或变量增量等原子操作来唤起竞争条件.这个测试详尽地试图展示这样一个种族:

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()
Run Code Online (Sandbox Code Playgroud)

我已经运行了上面的测试而没有失败(100x 100k多线程附加).任何人都可以让它失败吗?是否有另一类对象可以通过线程的原子,增量和修改来行为不端?

这些隐式"原子"语义是否适用于Python中的其他操作?这与GIL直接相关吗?

Tho*_*ers 7

附加到列表是线程安全的,是的.你只能在持有GIL时附加到列表中,并且列表在append操作期间注意不要释放GIL (毕竟,这是一个相当简单的操作.)不同线程的追加操作所经过的顺序是当然需要抓取,但它们都将是严格的序列化操作,因为GIL在附加期间永远不会被释放.

其他操作也不一定如此.Python中的大量操作可能会导致执行任意Python代码,从而导致GIL被释放.例如,i += 1是三个不同的操作,"获取i","向它添加1"并"将其存储在i"中."添加1"它将"转换(在这种情况下)" it.__iadd__(1),可以关闭并做任何它喜欢的事情.

Python对象本身保护自己的内部状态 - dicts不会被试图在其中设置项目的两个不同线程破坏.但是如果dict中的数据应该是内部一致的,那么dict和GIL都没有做任何事情来保护它,除了(以通常的线程方式)使它不太可能,但仍然可能的事情最终会比你想象的不同.