Python的并发并行[1] -> 线程[1] -> 多线程的建立与使用

多线程的建立与使用


目录

  1. 生成线程的三种方法
  2. 单线程与多线程对比
  3. 守护线程的设置

1 生成线程的三种方法

三种方式分别为:

  1. 创建一个Thread实例,传给它一个函数
  2. 创建一个Thread实例,传给它一个可调用的类实例
  3. 派生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()

2 单线程与多线程对比

利用单线程与多线程分别进行斐波那契,阶乘与累加操作,此处加入了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('
***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  
View Code

从结果中可以看出单线程耗时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  
View Code

运行主函数后可以看到,两个线程启动后,计数3次时,主线程已经结束,而这时由于t_2是非守护线程,因此主线程挂起等待t_2的计数结束之后,杀掉了还在运行的守护线程t_1,并且退出了主线程。

相关阅读


1. 基本概念

2. threading 模块

参考链接


《Python 核心编程 第3版》

原文地址:https://www.cnblogs.com/stacklike/p/8158997.html