在Python中,你可以启动一个线程,但却无法停止它。
进程特点:独立,每一个进程拥有自己的内存和数据栈等,所以只能采用进程间通信(IPC)的方式共共享信息。
线程特点:称为轻量级进程,一个进程可以拥有多个线程,它们共享相同的上下文,因此线程间的信息共享和通信更加容易。 在单核CPU中实现不了真正的并发,那么线程执行的时候会按照时分复用的方式完成伪并发。另一个需要注意的问题是,线程无法给予公平的执行时间。这是因为一些函数会在完成前保持阻塞状态,如果没有专门为多线程情况进行修改,会导致CPU的时间分配向这些贪婪的函数倾斜。
全局解释器锁:Python代码的执行是由Python虚拟机(又名解释器主循环)进行控制的。在Python的主循环中同时只能有一个控制线程在执行,就像单核CPU中的多进程一样。内存中可以有很多程序,但是在任意给定时刻只能有一个程序在运行。同理,尽管Python解释器中可以运行多个线程,但是在任意给定时刻只有一个线程会被解释器执行。因此,相比于Java等的多线程来说,Python的多线程会略显鸡肋。Python解释器由于设计时有GIL全局锁,导致了多线程无法利用多核。多线程的并发在Python中就是一个美丽的梦。详细信息:知乎-DarrenChan陈驰的回答 虽然Python的多线程无法实现多核利用,但是可以通过多进程来实现多核。
退出线程:当一个线程完成函数的执行时,它就会退出。另外还可以通过调用诸如thread.exit()之类的退出函数,或者sys.exit()之类的退出Python进程的标准方法,或者抛出SystemExit异常,来使线程退出。不过,不能直接“终止”一个线程。
相关模块:thread和threading。但是要尽量避免使用thread模块,而是使用更高级别的threading模块,它有更好的线程支持。thread模块只有一个同步原语,而threading有很多。且thread模块对于进程何时退出没有控制,当主线程结束时,其他所有线程也都强制结束,不会发出警告或进行适当的清理。而threading模块至少能保证重要的子线程在进程退出前结束。
守护线程:threading模块支持守护线程。守护线程一般是一个等待客户端请求服务的服务器。如果没有客户端请求,守护线程就是空闲的。如果把一个线程设置为守护线程,就表示这个线程是不重要的,进程退出时不需要等待这个线程执行完成。这种守护线程运行在一个无限循环里,在正常情况下不会退出。要将一个线程设置为守护线程,需要在启动线程之前执行如下赋值语句:thread.daemon = True。同样要检查线程的守护状态,只需要检查这个值即可。一个新的子线程会继承父线程的守护标记。整个Python程序(可以理解为主线程)将在所有非守护线程退出之后才退出。
threading模块:
threading.Thread类:是主要的执行对象。其重要的属性有name和daemon,后者作为一个标记线程是否守护进程的布尔标志。 Thread类重要的方法有:
- __init__(group=None, tatget=None, name=None, args=(),kwargs ={}, verbose=None, daemon=None)。实例化一个线程对象,需要有一个可调用的target,以及其参数args或kkwargs。还可以传递name或group参数。daemon的值会设定Thread.daemon值。
- start() : 开始执行该线程
- run() : 定义线程功能的方法,一般会在子类中被应用开发者重写。
- join(timeout = None) : 直至启动的线程终止之前一直挂起;除非给出了timeout(秒),否则会一直阻塞。
- 创建Thread的实例,传给它一个函数。
- 创建Thread的实例,传给它一个可以调用的类的实例。
- 派生Thread的子类,并创建子类的实例。
第一种方案编写很简单,只需要在创建Thread实例的时候 t = threading.Thread(target = func,args = ())。 其中func是我们希望线程执行的任务,args是该函数的参数,是一个元组变量。
第三种方案则要派生一个子类,比如
class myThread(threading.Thread):
def __init__(self,func,args,name=''):
threading.Thread.__init__(self)
self.func = func
self.args = args
self.name = name
def run(self):
self.func(*self.args)
首先要定义其构造函数__init__(),括号中是需要传入的参数。需要注意的是,在这个构造函数中,需要先把父类的构造函数threading.Thread.__init__(self)写在开头,再进行后续操作。 构造函数写完后,则通过重写run()方法的方式来指定我们希望这个线程完成的工作。重写完run()方法后,在后续的代码中得到myThread类的实例(比如t),再调用实例的t.start()方法,线程将会开始工作,执行run方法中的操作。
threading模块的其他函数
- active_count():返回当前活动的Thread对象个数
- current_thread():返回当前的Thread对象
- enumerate():返回当前活动的Thread对象列表
- settrace(func):为所有线程设置一个trace()函数
- setprofile(func):为所有线程设置一个profile()函数
- stack_size(size = 0):返回新创建线程的栈的大小;或为后续创建的线程设定栈的大小为size
由于Python虚拟机是单线程(GIL)的原因,只有线程在执行I/O密集型的应用时才能更好的发挥Python 的并发性。
计算密集型和I/O密集型任务
- 计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。
- IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。