Python学习笔记8——进程和线程

Python中的多进程

Unix和Linux操作系统上提供了fork()系统调用来创建进程,调用fork()函数的是父进程,创建出的是子进程,子进程是父进程的一个拷贝,但是子进程拥有自己的PID。fork()函数非常特殊它会返回两次,父进程中可以通过fork()函数的返回值得到子进程的PID,而子进程中的返回值永远都是0。Python的os模块提供了fork()函数。由于Windows系统没有fork()调用,因此要实现跨平台的多进程编程,可以使用multiprocessing模块的Process类来创建子进程,而且该模块还提供了更高级的封装,例如批量启动进程的进程池(Pool)、用于进程间通信的队列(Queue)和管道(Pipe)等。

 1 from multiprocessing import Process
 2 from os import getpid
 3 from random import randint
 4 from time import time, sleep
 5 
 6 
 7 def download_task(filename):
 8     print('启动下载进程,进程号[%d].' % getpid())
 9     print('开始下载%s...' % filename)
10     time_to_download = randint(5, 10)
11     sleep(time_to_download)
12     print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))
13 
14 
15 def main():
16     start = time()
17     p1 = Process(target=download_task, args=('Python从入门到住院.pdf', ))
18     p1.start()
19     p2 = Process(target=download_task, args=('Peking Hot.avi', ))
20     p2.start()
21     p1.join()
22     p2.join()
23     end = time()
24     print('总共耗费了%.2f秒.' % (end - start))
25 
26 
27 if __name__ == '__main__':
28     main()
在上面的代码中,我们通过Process类创建了进程对象,通过target参数我们传入一个函数来表示进程启动后要执行的代码,后面的args是一个元组,它代表了传递给函数的参数。Process对象的start方法用来启动进程,而join方法表示等待进程执行结束。运行上面的代码可以明显发现两个下载任务“同时”启动了,而且程序的执行时间将大大缩短,不再是两个任务的时间总和。

Python中的多线程

在Python早期的版本中就引入了thread模块(现在名为_thread)来实现多线程编程,然而该模块过于底层,而且很多功能都没有提供,因此目前的多线程开发我们推荐使用threading模块,该模块对多线程编程提供了更好的面向对象的封装。我们把刚才下载文件的例子用多线程的方式来实现一遍。

 1 from random import randint
 2 from threading import Thread
 3 from time import time, sleep
 4 
 5 
 6 def download(filename):
 7     print('开始下载%s...' % filename)
 8     time_to_download = randint(5, 10)
 9     sleep(time_to_download)
10     print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))
11 
12 
13 def main():
14     start = time()
15     t1 = Thread(target=download, args=('Python从入门到住院.pdf',))
16     t1.start()
17     t2 = Thread(target=download, args=('Peking Hot.avi',))
18     t2.start()
19     t1.join()
20     t2.join()
21     end = time()
22     print('总共耗费了%.3f秒' % (end - start))
23 
24 
25 if __name__ == '__main__':
26     main()

我们可以直接使用threading模块的Thread类来创建线程,但是我们之前讲过一个非常重要的概念叫“继承”,我们可以从已有的类创建新类,因此也可以通过继承Thread类的方式来创建自定义的线程类,然后再创建线程对象并启动线程。代码如下所示。

 1 from random import randint
 2 from threading import Thread
 3 from time import time, sleep
 4 
 5 
 6 class DownloadTask(Thread):
 7 
 8     def __init__(self, filename):
 9         super().__init__()
10         self._filename = filename
11 
12     def run(self):
13         print('开始下载%s...' % self._filename)
14         time_to_download = randint(5, 10)
15         sleep(time_to_download)
16         print('%s下载完成! 耗费了%d秒' % (self._filename, time_to_download))
17 
18 
19 def main():
20     start = time()
21     t1 = DownloadTask('Python从入门到住院.pdf')
22     t1.start()
23     t2 = DownloadTask('Peking Hot.avi')
24     t2.start()
25     t1.join()
26     t2.join()
27     end = time()
28     print('总共耗费了%.2f秒.' % (end - start))
29 
30 
31 if __name__ == '__main__':
32     main()

比较遗憾的一件事情是Python的多线程并不能发挥CPU的多核特性,这一点只要启动几个执行死循环的线程就可以得到证实了。之所以如此,是因为Python的解释器有一个“全局解释器锁”(GIL)的东西,任何线程执行前必须先获得GIL锁,然后每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行,这是一个历史遗留问题,但是即便如此,就如我们之前举的例子,使用多线程在提升执行效率和改善用户体验方面仍然是有积极意义的。

 

原文地址:https://www.cnblogs.com/yuanxixing/p/12322445.html