Python中的线程编程

6 投票
6 回答
3772 浏览
提问于 2025-04-15 11:59

我刚接触线程编程,想知道在服务器环境中,创建很多线程来处理各种任务是不是不太好。相比于更简单的线性编程,线程会占用更多的内存和CPU吗?

6 个回答

4

这要看你用的平台。

在Windows系统中,创建线程时大约需要占用1MB的内存。与其像疯了一样不停创建线程,不如使用线程池,这样可以确保你创建的线程数量不会超过一个固定的上限。而且在Python中,你还要面对一个叫做全局解释器锁的东西,这会影响到那些需要很多并发线程的代码。

在Unix系统中,你可以考虑使用不同的进程,而不是线程,或者尝试其他异步处理的方法(比如Twisted这个服务器框架,它有一些很有趣的方式来处理异步网络任务。如果你想尝试更冒险的东西,可以看看stackless Python,这是一种不使用内核线程的延续框架)。

5

既然你刚接触线程,有一点很重要的事情需要记住——我更喜欢把它看作是值的并行作用范围。

在传统的线性编程中,对于任何给定的对象,只有一个线程在访问和修改一段数据。这通常是安全的,因为有词法作用域。具体来说,一个函数可以操作一个变量的值,而不会影响全局值。如果没有词法作用域,或者词法作用域很差,那么在一个函数中改变名为“foo”的变量的值,就会影响到另一个名为“foo”的变量。这种问题现在不太常见,但仍然足够普遍,值得提及。

在使用线程时,你会遇到类似的问题,只是更微妙一些。虽然词法作用域仍然在帮助你——在一个函数内部的局部值“X”与另一个函数内部的局部值“X”是独立的,但数据结构是可变的,这在多线程中是导致错误的主要原因。

具体来说,如果一个函数接收到一个可变的值,那么在多线程环境中,除非你特别小心,否则这个函数无法保证这个值不会被其他地方改变。

这种共享状态可能是多线程系统中90%到99%的错误来源,而且很难调试。因此,如果你要编写一个多线程系统,你应该考虑一下你的共享值会传播多远——也就是并行访问的作用范围。

为了减少错误,你有几种已知有效的选择:

  1. 不使用共享状态——通过线程安全的队列传递共享数据
  2. 在所有共享数据周围加锁,并确保在代码中始终如一地使用。这有时比人们想象的要难得多。问题在于当你“忘记”去锁定一个对象时——这对人来说是非常容易发生的。
  3. 有一个单一的对象——一个拥有者——负责共享状态。允许客户端线程向它请求共享状态中的值的副本,并附带一个令牌。当它们想要更新共享状态时,便将消息和它们的令牌一起传回给这个拥有者。然后,拥有者可以判断是否发生了更新冲突。

第一种方法最类似于Unix管道。第三种方法在逻辑上等同于版本控制,通常被称为软件事务内存。

第一和第三种方法是Kamaelia支持的并发模式,旨在消除由第二种方法引起的错误。(顺便说一下,我负责Kamaelia项目)第二种方法不被支持,因为它依赖于“总是做对所有事情”。

无论你使用哪种方法来解决问题,考虑到这个问题以及处理它的方法,并提前规划你打算如何应对,将会为你省去很多麻烦。

19

如果你想使用多个线程,有几个事情需要考虑:

  1. 你只能同时运行和处理器数量一样多的线程。(这很明显)
  2. 在Python中,每个线程都是一个“内核线程”,通常会占用不少资源(在Linux上默认是8MB的栈空间)
  3. Python有一个全局解释器锁,这意味着无论处理器有多少,只有一个Python指令可以被独立处理。不过,如果你的线程在等待输入输出(IO),这个锁会被释放。

我从中得出的结论是:

  1. 如果你在进行输入输出操作(比如使用Turbogears或Twisted),或者使用正确编写的扩展模块(比如numpy),可以使用线程。
  2. 如果你想同时执行Python代码,使用进程会更简单(可以用multiprocess模块来实现)。

撰写回答