Python的并发并行[1] -> 线程[1] -> 多线程的建立与使用
多线程的建立与使用
目录
三种方式分别为:
- 创建一个Thread实例,传给它一个函数
- 创建一个Thread实例,传给它一个可调用的类实例
- 派生Thread的子类,并创建子类的实例
# There are three ways to create a thread # The first is create a thread instance, and pass a function # The second one is create a thread instance, and pass the callable instance(obj) # The third one is create a subclass of Thread, then generate an instance
1.1 创建Thread实例并传递一个函数
在Thread类实例化时,将函数(及其参数)传入,下面的例子中,在实例化Thread类的时候传入了一个函数loop及其参数,生成了一个线程的实例,再启动线程。
1 # Method one: pass function 2 from threading import Thread 3 from time import sleep, ctime 4 5 loops = [4, 2] 6 7 def loop(nloop, nsec): 8 print('start loop', nloop, 'at', ctime()) 9 sleep(nsec) 10 print('loop', nloop, 'done at:', ctime()) 11 12 def main(): 13 print('starting at:', ctime()) 14 threads = [] 15 nloops = range(len(loops)) 16 17 # Create all threads 18 for i in nloops: 19 t = Thread(target=loop, args=(i, loops[i])) 20 threads.append(t) 21 for i in threads: 22 i.start() 23 for i in threads: 24 i.join() 25 print('All DONE at:', ctime()) 26 27 if __name__ == '__main__': 28 main()
1.2 创建Thread实例并传递一个可调用的类实例
同样,在Thread类实例化时,类(及其参数)传入,下面的例子中,在实例化Thread类的时候传入了一个可调用(__call__函数使类变为可调用)的类实例ThreadFunc,且完成初始化传给target=,此时生成了一个线程的实例,随后启动线程。
1 # Method two: pass object 2 3 from threading import Thread 4 from time import sleep, ctime 5 6 loops = [4, 2] 7 8 class ThreadFunc(object): 9 def __init__(self, func, args, name=''): 10 self.name = name 11 self.func = func 12 self.args = args 13 14 def __call__(self): 15 self.func(*self.args) 16 17 def loop(nloop, nsec): 18 print('start loop', nloop, 'at', ctime()) 19 sleep(nsec) 20 print('loop', nloop, 'done at:', ctime()) 21 22 def main(): 23 print('starting at:', ctime()) 24 threads = [] 25 nloops = range(len(loops)) 26 27 # Create all threads 28 for i in nloops: 29 t = Thread(target=ThreadFunc(loop, (i, loops[i]), loop.__name__)) 30 threads.append(t) 31 for i in threads: 32 i.start() 33 for i in threads: 34 i.join() 35 print('All DONE at:', ctime()) 36 37 if __name__ == '__main__': 38 main()
1.3 派生Thread子类并生成子类实例
以Thread为基类,派生出一个子类,在子类中重定义run方法,最终生成一个线程实例进行调用。下面的例子中,生成了一个子类MyThread,同时重定义run函数,在初始化时接收一个func参数作为调用函数。
1 # Method three: by subclass 2 3 from threading import Thread 4 from time import sleep, ctime 5 6 loops = [4, 2] 7 8 class MyThread(Thread): 9 def __init__(self, func, args, name=''): 10 Thread.__init__(self) 11 self.name = name 12 self.func = func 13 self.args = args 14 15 def run(self): 16 self.func(*self.args) 17 18 def loop(nloop, nsec): 19 print('start loop', nloop, 'at', ctime()) 20 sleep(nsec) 21 print('loop', nloop, 'done at:', ctime()) 22 23 def main(): 24 print('starting at:', ctime()) 25 threads = [] 26 nloops = range(len(loops)) 27 28 # Create all threads 29 for i in nloops: 30 t = MyThread(loop, (i, loops[i]), loop.__name__) 31 threads.append(t) 32 for i in threads: 33 i.start() 34 for i in threads: 35 i.join() 36 print('All DONE at:', ctime()) 37 38 if __name__ == '__main__': 39 main()
利用单线程与多线程分别进行斐波那契,阶乘与累加操作,此处加入了sleep进行计算延迟,这是由于Python解释器的GIL特性使得Python对于计算密集型的函数并没有优势,而对于I/O密集型的函数则优化性能较好。
1 from threading import Thread 2 import time 3 from time import ctime 4 5 6 class MyThread(Thread): 7 """ 8 Bulid up a Module to make this subclass more general 9 And get return value by add a function named 'getResult()' 10 """ 11 def __init__(self, func, args, name=''): 12 Thread.__init__(self) 13 self.name = name 14 self.func = func 15 self.args = args 16 17 def getResult(self): 18 return self.res 19 20 def run(self): 21 print('Starting', self.name, 'at:', ctime()) 22 # Call function here and calculate the running time 23 self.res = self.func(*self.args) 24 print(self.name, 'finished at:', ctime()) 25 26 def fib(x): 27 time.sleep(0.005) 28 if x < 2: 29 return 1 30 return (fib(x-2) + fib(x-1)) 31 32 def fac(x): 33 time.sleep(0.1) 34 if x < 2: 35 return 1 36 return (x * fac(x-1)) 37 38 def sumx(x): 39 time.sleep(0.1) 40 if x < 2: 41 return 1 42 return (x + sumx(x-1)) 43 44 funcs = [fib, fac, sumx] 45 n = 12 46 47 def main(): 48 nfuncs = range(len(funcs)) 49 print('***SINGLE THREADS') 50 for i in nfuncs: 51 print('Starting', funcs[i].__name__, 'at:', ctime()) 52 print(funcs[i](n)) 53 print(funcs[i].__name__, 'finished at:', ctime()) 54 55 print('\n***MULTIPLE THREADS') 56 threads = [] 57 for i in nfuncs: 58 t = MyThread(funcs[i], (n,), funcs[i].__name__) 59 threads.append(t) 60 for i in nfuncs: 61 threads[i].start() 62 for i in nfuncs: 63 threads[i].join() 64 print(threads[i].getResult()) 65 print('All DONE') 66 67 if __name__ == '__main__': 68 main()
第 1-24 行,导入所需模块,并派生线程的子类,定义一个返回函数用于返回结果,
第 26-45 行,分别定义斐波那契,阶乘与累加函数,
最后在主函数中分别运行两种模式的计算,得到结果
***SINGLE THREADS Starting fib at: Tue Aug 1 20:17:47 2017 233 fib finished at: Tue Aug 1 20:17:50 2017 Starting fac at: Tue Aug 1 20:17:50 2017 479001600 fac finished at: Tue Aug 1 20:17:51 2017 Starting sumx at: Tue Aug 1 20:17:51 2017 78 sumx finished at: Tue Aug 1 20:17:52 2017 ***MULTIPLE THREADS Starting fib at: Tue Aug 1 20:17:52 2017 Starting fac at: Tue Aug 1 20:17:52 2017 Starting sumx at: Tue Aug 1 20:17:52 2017 sumx finished at: Tue Aug 1 20:17:53 2017 fac finished at: Tue Aug 1 20:17:53 2017 fib finished at: Tue Aug 1 20:17:54 2017 233 479001600 78 All DONE
从结果中可以看出单线程耗时5秒,而多线程耗时2秒,优化了程序的运行速度。
Note: 再次注明,Python的多线程并未对计算性能有所提升,此处是由于加入了sleep的等待,因此使得Python的GIL发挥其优势。
守护线程一般属于后台无限循环的程序,主线程会在所有非守护线程结束之后,自动关闭还在运行的守护线程,而不会等待它的无限循环完成。守护线程的设置只要将线程实例的daemon设置为True即可,默认是False。
1 import threading 2 import time 3 import random 4 5 class MyThread(threading.Thread): 6 def __init__(self, count): 7 threading.Thread.__init__(self) 8 print('%s: There are %d numbers need to be counted' % (self.name, count)) 9 self.count = count 10 11 def run(self): 12 name = self.name 13 for i in range(0, self.count): 14 print('%s counts %d' % (name, i)) 15 time.sleep(1) 16 print('%s finished' % name) 17 18 19 def main(): 20 print('-------Starting-------') 21 count = random.randint(4, 7) 22 t_1 = MyThread(count*count) 23 t_2 = MyThread(count) 24 t_1.daemon = True 25 t_2.daemon = False 26 t_1.start() 27 t_2.start() 28 time.sleep(3) 29 print('---Main Thread End---') 30 31 if __name__ == '__main__': 32 main() 33
第 5-16 行,派生一个线程子类,run函数实现每秒计数一次的功能
第 19-29 行,在主函数中,分别生成两个线程实例,其中t_1计数量较大,设为守护线程,t_2计数量较小,设为非守护线程,线程均不挂起,主线程在3秒后会结束。
运行得到结果
-------Starting------- Thread-1: There are 36 numbers need to be counted Thread-2: There are 6 numbers need to be counted Thread-1 counts 0 Thread-2 counts 0 Thread-1 counts 1 Thread-2 counts 1 Thread-1 counts 2 Thread-2 counts 2 Thread-1 counts 3 ---Main Thread End--- Thread-2 counts 3 Thread-1 counts 4 Thread-2 counts 4 Thread-1 counts 5 Thread-2 counts 5 Thread-1 counts 6 Thread-2 finished
运行主函数后可以看到,两个线程启动后,计数3次时,主线程已经结束,而这时由于t_2是非守护线程,因此主线程挂起等待t_2的计数结束之后,杀掉了还在运行的守护线程t_1,并且退出了主线程。
相关阅读
1. 基本概念
2. threading 模块
参考链接
《Python 核心编程 第3版》