为什么这个python代码不是线程安全的?

时间:2017-03-02 19:38:17

标签: python multithreading python-3.x concurrency

我把它作为学校作业的一部分交给了,并且标记它的人提到这部分不是线程安全的。

分配是在python中创建一个多线程套接字服务器,接受一个数字并返回该数字的Fibonacci值。我的方法是通过在每个线程之间共享字典来记忆计算。

这是代码(为了简洁起见,有错误处理和删除)

from socketserver import ThreadingMixIn, TCPServer, BaseRequestHandler


class FibonacciThreadedTCPServer(ThreadingMixIn, TCPServer):
    def __init__(self, server_address):
        TCPServer.__init__(self, server_address, FibonacciThreadedTCPRequestHandler, bind_and_activate=True)
        #this dictionary will be shared between all Request handlers
        self.fib_dict = {0: 0, 1: 1, 2: 1}


class FibonacciThreadedTCPRequestHandler(BaseRequestHandler):
    def handle(self):
        data = self.request.recv(1024).strip()
        num = int(data)
        result = self.calc_fib(self.server.fib_dict, num)
        ret = bytes(str(result) + '\n', 'ascii')
        self.request.sendall(ret)


    @staticmethod
    def calc_fib(fib_dict, n):
        """
        Calculates the fibonacci value of n using a shared lookup table and a linear calculation.
        """
        length = len(fib_dict)
        while length <= n:
            fib_dict[length] = fib_dict[length - 1] + fib_dict[length - 2]
            length = len(fib_dict)
        return fib_dict[n]

我知道在calc_fib方法中同时发生了读写操作,通常这意味着代码不是线程安全的。但是在这种情况下,我认为可以证明代码总能提供可预测的结果。

是否可以同时进行读写操作,以使其不被视为线程安全?或者,如果它总是返回可靠的结果,则被认为是线程安全的。

为什么我认为此代码始终会产生可靠的结果:

  1. 在字典中发生写入之前,字典中的任何给定索引都不会发生读取。

  2. 对任何给定索引的任何后续写入都将包含与先前写入相同的编号,因此无论何时发生读/写序列,它都将始终接收相同的数据。

  3. 我已经通过在每个操作之间添加随机睡眠并同时发出数百个线程的请求来测试它,并且在我的测试期间已经返回了正确的答案。

    任何想法或批评都将受到赞赏。感谢。

2 个答案:

答案 0 :(得分:3)

在这种特殊情况下,the GIL应该保护您的代码安全,因为:

  1. 通过GIL保护CPython内置数据结构免受实际损害(而不仅仅是不正确的行为)(dict特别是需要此保证,因为类实例和非本地范围通常使用dict s进行属性/名称查找,如果没有GIL,只需读取值就会充满危险)
  2. 您正在更新长度的缓存值,然后将其用于下一组操作,而不是在突变期间重新检查长度;这可能导致重复工作(多个线程看到旧的长度并重复计算新值),但由于密钥总是设置为相同的值,所以如果它们各自独立设置它并不重要
  3. 您永远不会从缓存中删除(如果您这样做,缓存长度会让您感到厌烦)
  4. 所以在CPython中,这应该没问题。我不能为其他Python解释器做任何保证;没有GIL,如果他们在没有内部锁定的情况下实现他们的dict,那么完全可能由一个线程中的写入触发的rehash操作可能导致另一个线程从dict中读取一个不一致的内容/无法使用状态。

答案 1 :(得分:2)

首先,为什么你认为词典是线程安全的?我快速搜索了Python3文档(我自己就是Python新手),我找不到任何保证两个未同步的线程可以安全地更新同一个字典而不会破坏字典内部并且可能导致程序崩溃。

自1980年以来,我一直在用其他语言编写多线程代码 - 我已经学会了永远不要相信某些东西是线程安全的,因为它在我测试它时会这样做。我希望看到文档假设是线程安全的。否则,我会在它周围抛出一个互斥锁。

其次,您假设fib_dict[length - 1]fib_dict[length - 2]有效。我对其他编程语言的经验表明不要假设它。在其他编程语言(例如,Java)中,当线程在没有同步的情况下共享数据时,一个线程可能看到变量更新以与其他一些线程执行它们的顺序不同的顺序发生。例如,理论上可以在没有同步的情况下访问Map的Java线程看到size()增加的Map,然后才能看到新值实际出现在中< / em>地图。我会假设类似的事情可能会在Python中发生,直到有人向我展示其他说明的官方文档。