Python多线程编程非常类似于linux C的pthread多线程库,只不过以面向对象的形式提供。Cpython 由于解释器实现策略的问题,增加了GIL(Global Interpreter Lock)全局解释器锁,使得python多线程在同一时刻只能有一个线程执行。因此,在使用Cpython时,对于CPU密集型任务,我们最好不采用python多线程方案,而是使用多进程方案。但对于IO密集型任务,由于每次进行IO的时候,python会释放GIL锁,多线程方案还是可取的。
python的多线程是封装在threading模块中的,有两种方式编写多线程程序,一种是创建一个Thread对象,然后调用对象的start()方法,另一种是继承Thread类,然后重写run()方法。
创建Thread类对象的示例代码如下:
import os, sys import threading import time def my_thread(someone): print('[{}]: {} begin working'.format(threading.currentThread(), someone)) time.sleep(2) print('[{}]: {} finished working'.format(threading.currentThread(), someone)) if __name__ == '__main__': t1 = threading.Thread(target = my_thread, args = ('Jack',)) t2 = threading.Thread(target = my_thread, args = ('Edward',)) t1.setDaemon(True) t2.setDaemon(True) t1.start() t2.start() t1.join() t2.join() print('[%s] Main thread finished' % threading.currentThread())
运行结果:
[<Thread(Thread-1, started daemon 140397066352384)>]: Jack begin working
[<Thread(Thread-2, started daemon 140397057959680)>]: Edward begin working
[<Thread(Thread-1, started daemon 140397066352384)>]: Jack finished working
[<Thread(Thread-2, started daemon 140397057959680)>]: Edward finished working
[<_MainThread(MainThread, started 140397088982784)>] Main thread finished
说明:setDaemon(True)方法使得调用线程(主线程)成为被调用线程(t1, t2)的守护线程,当调用线程(守护线程)退出时,子线程立马被回收。反之,setDaemon(False)(默认)情况下,调用线程退出是,子线程继续运行,直到自行退出。
join()方法使得调用线程(主线程)阻塞,直到被调用线程(t1,t2)主动退出。
继承Thread类示例代码如下:
import os, sys import threading import time class MyThread(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): print('[%s] begin' % threading.currentThread().getName()) time.sleep(2) print('[%s] end' % threading.currentThread().getName()) if __name__ == '__main__': t1 = MyThread() t2 = MyThread() t1.setDaemon(True) t2.setDaemon(True) t1.start() t2.start() t1.join() t2.join() print('[%s] Main thread finished' % threading.currentThread())
运行结果:
[Thread-1] begin
[Thread-2] begin
[Thread-1] end[Thread-2] end
[<_MainThread(MainThread, started 140297001658112)>] Main thread finished
threading模块还提供了线程互斥锁threading.Lock和threading.RLock,并提供了acquire()和release()两个接口用于获取和释放锁,以Lock为例示例代码如下:
import os, sys import threading import time a = 10 lock = threading.Lock() def my_thread(*args): while True: lock.acquire() try: global a if args[0] == 1: a = 1 elif args[0] == 2: a = 2 print('[%s]: %d' % (threading.currentThread().getName(), a)) finally: lock.release() if __name__ == '__main__': t1 = threading.Thread(target = my_thread, args = (1,)) t2 = threading.Thread(target = my_thread, args = (2,)) t1.setDaemon(True) t2.setDaemon(True) t1.start() t2.start() t1.join() t2.join() print('[%s] Main thread finished' % threading.currentThread())
很多语言都提供了ThreadLocal(Java,C++)线程本地变量,python也不例外(threading.local)。我们知道,如果多个线程共享同一个对象属性,传统模式下,对该该属性的修改与访问需要加上线程互斥锁,比如 threading.Lock。但是互斥锁的系统开销比较大:锁定的资源同一时刻串行执行,线程切换开销。ThreadLocal通过给每个线程分配一个共享资源的副本,达到每线程资源独占的效果,从而避免了线程锁的开销,示例代码如下:
import os, sys import threading import time a = threading.local() def my_thread(*args): while True: if args[0] == 1: a = 1 elif args[0] == 2: a = 2 print('[%s]: %d' % (threading.currentThread().getName(), a)) if __name__ == '__main__': t1 = threading.Thread(target = my_thread, args = (1,)) t2 = threading.Thread(target = my_thread, args = (2,)) t1.setDaemon(True) t2.setDaemon(True) t1.start() t2.start() t1.join() t2.join() print('[%s] Main thread finished' % threading.currentThread())