Python多线程用法
基本用法
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import thread
import time
# 为线程定义一个函数
def print_time( threadName, delay):
count = 0
while count < 5:
time.sleep(delay)
count += 1
print "%s: %s" % ( threadName, time.ctime(time.time()) )
# 创建两个线程
try:
thread.start_new_thread( print_time, ("Thread-1", 2, ) )
thread.start_new_thread( print_time, ("Thread-2", 4, ) )
except:
print("Error: unable to start thread")
获取线程函数执行结果
线程搭载的函数不一定是无返回值的,对于需要获取返回值的多线程任务我们应该怎么做呢?
Python自带的Thread类是无法实现这个功能的,最合理的解决方案是继承Thread类,用自定义的Thread类维护函数执行结果,示例代码如下:
import threading,time
class ThreadWithResult(threading.Thread):
def __init__(self, func, args=()):
super(ThreadWithResult, self).__init__()
self.func = func
self.args = args
def run(self):
time.sleep(1)
self.result = self.func(*self.args)
def get_result(self):
threading.Thread.join(self) # get_result 自带join
try:
return self.result
except Exception:
return None
这个Thread的子类当中实现了get_result()方法,这个方法包含了join(),因此这个线程启动后,如果要主线程等待子线程结束,只需要调用get_result()即可,不需要再join()这个线程。使用方法如下:
# 接之前的代码
def add (a, b):
return a + b
if __name__=="__main__":
data=[[1,2],[3,4],[5,6]]
thread_list = []
sum_result = 0
for i in range(3):
t= ThreadWithResult(add, (data[i][0], data[i][1]))
t.start()
thread_list.append(t)
for t in thread_list:
sum_result+=t.get_result()
print(f"Sum:{sum_result}")
Python多线程无法利用多核CPU计算性能
在使用Python的多线程之前需要先明确一个概念:Python的多线程并不是完整的多线程!
Python本质上还是一个解释型的语言(这也是为什么python性能不太好的一个原因),Python代码的执行由Python虚拟机(也被称为解释器)来控制。
Python在设计之初有一个避免冲突的考虑,那就是在解释器中同时只有一个线程在执行,就像单CPU的系统中运行多个进程那样,内存中可以存放多个程序,但任意时刻,只有一个程序在CPU中运行。同样地,虽然Python解释器可以运行多个线程,只有一个线程在解释器中运行。
Python虚拟机某一个时刻执行那条代码是一个排斥性的机制,因此需要用锁来控制,目前的Python虚拟机实现的机制是提供一个全局解释器锁(GIL),拿到这个锁的代码才能在解释器中运行,也就是说这个锁限制了一个Python虚拟机中同时只有一个线程在运行。
在Python的多线程环境中,Python虚拟机在线程间切换的机制主要分为以下几步:
- 获取GIL锁。
- 切换到一个线程去执行。
- 运行。
- 把线程设置为睡眠状态。
- 解锁GIL。
- 再次重复以上步骤。
在单核系统中,这个机制没有任何问题,但是一旦到了多核cpu的时代,这个机制就开始严重拖python性能后腿。其他语言如c,c++可以只启动一个进程,然后在这个进程中创建多个线程,再利用多线程调度机制充分利用多核cpu的性能。然而在Python中,如果你只启动一个python虚拟机,那么不管你有几个核,这个虚拟机只能利用起来一个核心,然后用时间片轮转的方式来切换线程,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。这是GIL机制的历史遗留问题。通常情况下我们用的解释器是官方实现的CPython,如果执意要用python多线程来利用多核的性能,那就只能自己实现一个不用GIL的解释器,估计没什么人会这么做吧。
Python多线程的意义
看完上一节,估计有人会问,既然python多线程无法利用起来多核cpu计算性能,线程上下文切换还要浪费资源,那么Python多线程还有什么意义?
一个程序不止有计算需要消耗性能,还有IO性能的消耗,有的IO操作是非常耗时的,如网络连接,对于IO密集型程序,就可以利用多线程避免单线程的阻塞。最典型的IO密集型程序就是web应用,这种应用对计算性能需求不大,因此可以用多线程机制创建数十乃至数千的线程来处理web请求,这样即使只有单核也可以同时处理大量的web请求。
不过现在很多语言新的feature使得比线程更轻量级的协程称为可能,协程甚至不需要维护线程栈,几乎毫无上下文切换的开销,是IO密集型程序的首选,python也可以做到在单线程内用yeild语句实现协程,具体实现方法未来专门写一篇博客来讲。
如何利用多核CPU计算性能
那我们如果遇到计算密集的任务怎么办?我们就没法利用多核cpu了吗?如果要在python中利用多核cpu应该怎么办?
那就是更加笨重的多进程了,不过已经用python了,多进程增加的负担也不算什么了吧~
python提供了自带的多进程库 Multiprocessing,python多进程将在另一篇博客当中做学习。
但是最根本的建议是:如果真的要在工业界做计算密集的任务不要用python来做