等待线程3秒返回结果_Python基础教程:进程和线程(上)
发布日期:2021-09-14 01:32:08 浏览次数:3 分类:技术文章

本文共 6213 字,大约阅读时间需要 20 分钟。

最近会开始给大家出些Python进阶系列的文章,这是该系列的第一篇文章,介绍进程和线程的知识,本次的Python基础教程则跟大家介绍下进程和线程的概念,多进程和多线程各自的实现方法和优缺点,以及分别在哪些情况采用多进程,或者是多线程。

概念

并发编程就是实现让程序同时执行多个任务,而如何实现并发编程呢,这里就涉及到进程线程这两个概念。

对于操作系统来说,一个任务(或者程序)就是一个进程(Process),比如打开一个浏览器是开启一个浏览器进程,打开微信就启动了一个微信的进程,打开两个记事本,就启动两个记事本进程。

进程的特点有:

  • 操作系统以进程为单位分配存储空间, 每个进程有自己的地址空间、数据栈以及其他用于跟踪进程执行的辅助数据;
  • 进程可以通过 fork 或者 spawn 方式创建新的进程来执行其他任务
  • 进程都有自己独立的内存空间,所以进程需要通过进程间通信机制(IPC,Inter-Process Communication)来实现数据共享,具体的方式包括管道、信号、套接字、共享内存区

一个进程还可以同时做多件事情,比如在 Word 里面同时进行打字、拼音检查、打印等事情,也就是一个任务分为多个子任务同时进行,这些进程内的子任务被称为线程(Thread)

dddca17ec20ab132c8a1f9bf5c2f9fbf.png

因为每个进程至少需要完成一件事情,也就是一个进程至少有一个线程。当要实现并发编程,也就是同时执行多任务时,有以下三种解决方案:

  • 多进程,每个进程只有一个线程,但多个进程一起执行多个任务;
  • 多线程,只启动一个进程,但一个进程内开启多个线程;
  • 多进程+多线程,即启动多个进程,每个进程又启动多个线程,但这种方法非常复杂,实际很少使用

注意:真正的并行执行多任务只有在多核 CPU 上才可以实现,单核 CPU 系统中,真正的并发是不可能的,因为在某个时刻能够获得CPU的只有唯一的一个线程,多个线程共享了CPU的执行时间

Python 是同时支持多进程和多线程的,下面就分别介绍多进程和多线程。

多进程

在 Unix/Linux 系统中,提供了一个 fork() 系统调用,它是一个特殊的函数,普通函数调用是调用一次,返回一次但 fork 函数调用一次,返回两次,因为调用该函数的是父进程,然后复制出一份子进程了,最后同时在父进程和子进程内返回,所以会返回两次。

子进程返回的永远是 0 ,而父进程会返回子进程的 ID,因为父进程可以复制多个子进程,所以需要记录每个子进程的 ID,而子进程可以通过调用 getpid() 获取父进程的 ID。

Python 中 os 模块封装了常见的系统调用,这就包括了 fork ,代码示例如下:

import osprint('Process (%s) start...' % os.getpid())# Only works on Unix/Linux/Mac:pid = os.fork()if pid == 0: print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))else: print('I (%s) just created a child process (%s).' % (os.getpid(), pid))

运行结果:

Process (876) start...I (876) just created a child process (877).I am child process (877) and my parent is 876.

由于 windows 系统中是不存在 fork ,所以上述函数无法调用,但 Python 是跨平台的,所以也还是有其他模块可以实现多进程的功能,比如 multiprocessing模块。

multiprocess

multiprocessing 模块中提供了 Process 类来代表一个进程对象,接下来用一个下载文件的例子来说明采用多进程和不用多进程的差别。

首先是不采用多进程的例子:

def download_task(filename): '''模拟下载文件''' print('开始下载%s...' % filename) time_to_download = randint(5, 10) sleep(time_to_download) print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))def download_without_multiprocess(): '''不采用多进程''' start = time() download_task('Python.pdf') download_task('nazha.mkv') end = time() print('总共耗费了%.2f秒.' % (end - start))if __name__ == '__main__': download_without_multiprocess()

运行结果如下,这里用 randint 函数来随机输出当前下载文件的耗时,从结果看,程序运行时间等于两个下载文件的任务时间总和。

开始下载Python.pdf...Python.pdf下载完成! 耗费了9秒开始下载nazha.mkv...nazha.mkv下载完成! 耗费了9秒总共耗费了18.00秒.

如果是采用多进程,例子如下所示:

def download_task(filename): '''模拟下载文件''' print('开始下载%s...' % filename) time_to_download = randint(5, 10) sleep(time_to_download) print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))def download_multiprocess(): '''采用多进程''' start = time() p1 = Process(target=download_task, args=('Python.pdf',)) p1.start() p2 = Process(target=download_task, args=('nazha.mkv',)) p2.start() p1.join() p2.join() end = time() print('总共耗费了%.2f秒.' % (end - start))if __name__ == '__main__': download_multiprocess()

这里多进程例子中,我们通过 Process 类创建了进程对象,通过 target 参数传入一个函数表示进程需要执行的任务,args 是一个元组,表示传递给函数的参数,然后采用 start 来启动进程,而 join 方法表示等待进程执行结束。

运行结果如下所示,耗时就不是两个任务执行时间总和,速度上也是大大的提升了。

开始下载Python.pdf...开始下载nazha.mkv...Python.pdf下载完成! 耗费了5秒nazha.mkv下载完成! 耗费了9秒总共耗费了9.36秒.

Pool

上述例子是开启了两个进程,但如果需要开启大量的子进程,上述代码的写法就不合适了,应该采用进程池的方式批量创建子进程,还是用下载文件的例子,但执行下部分的代码如下所示:

import osfrom multiprocessing import Process, Poolfrom random import randintfrom time import time, sleepdef download_multiprocess_pool(): '''采用多进程,并用 pool 管理进程池''' start = time() filenames = ['Python.pdf', 'nazha.mkv', 'something.mp4', 'lena.png', 'lol.avi'] # 进程池 p = Pool(5) for i in range(5): p.apply_async(download_task, args=(filenames[i], )) print('Waiting for all subprocesses done...') # 关闭进程池 p.close() # 等待所有进程完成任务 p.join() end = time() print('总共耗费了%.2f秒.' % (end - start))if __name__ == '__main__': download_multiprocess_pool()

代码中 Pool 对象先创建了 5 个进程,然后 apply_async 方法就是并行启动进程执行任务了,调用 join() 方法之前必须先调用 close() ,close() 主要是关闭进程池,所以执行该方法后就不能再添加新的进程对象了。然后 join() 就是等待所有进程执行完任务。

运行结果如下所示:

Waiting for all subprocesses done...开始下载Python.pdf...开始下载nazha.mkv...开始下载something.mp4...开始下载lena.png...开始下载lol.avi...nazha.mkv下载完成! 耗费了5秒lena.png下载完成! 耗费了6秒something.mp4下载完成! 耗费了7秒Python.pdf下载完成! 耗费了8秒lol.avi下载完成! 耗费了9秒总共耗费了9.80秒.

子进程

大多数情况,子进程是一个外部进程,而非自身。在创建子进程后,我们还需要控制子进程的输入和输出。

subprocess 模块可以让我们很好地开启子进程以及管理子进程的输入和输出。

下面是演示如何用 Python 演示命令 nslookup www.python.org,代码如下所示:

import subprocessprint('$ nslookup www.python.org')r = subprocess.call(['nslookup', 'www.python.org'])print('Exit code:', r)

运行结果:

$ nslookup www.python.orgServer: 192.168.19.4Address: 192.168.19.4#53Non-authoritative answer:www.python.org canonical name = python.map.fastly.net.Name: python.map.fastly.netAddress: 199.27.79.223Exit code: 0

如果子进程需要输入,可以通过 communicate() 进行输入,代码如下所示:

import subprocessprint('$ nslookup')p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)output, err = p.communicate(b'set q=mxpython.orgexit')print(output.decode('utf-8'))print('Exit code:', p.returncode)

这段代码就是执行命令 nslookup 时,输入:

set q=mxpython.orgexit

运行结果:

$ nslookupServer: 192.168.19.4Address: 192.168.19.4#53Non-authoritative answer:python.org mail exchanger = 50 mail.python.org.Authoritative answers can be found from:mail.python.org internet address = 82.94.164.166mail.python.org has AAAA address 2001:888:2000:d::a6Exit code: 0

进程间通信

进程之间是需要通信的,multiprocess 模块中也提供了 Queue、Pipes 等多种方式来交换数据。

这里以 Queue 为例,在父进程创建两个子进程,一个往 Queue 写入数据,另一个从 Queue 读取数据。代码如下:

import osfrom multiprocessing import Process, Queueimport randomfrom time import time, sleep# 写数据进程执行的代码:def write(q): print('Process to write: %s' % os.getpid()) for value in ['A', 'B', 'C']: print('Put %s to queue...' % value) q.put(value) sleep(random.random())# 读数据进程执行的代码:def read(q): print('Process to read: %s' % os.getpid()) while True: value = q.get(True) print('Get %s from queue.' % value)def ipc_queue(): ''' 采用 Queue 实现进程间通信 :return: ''' # 父进程创建Queue,并传给各个子进程: q = Queue() pw = Process(target=write, args=(q,)) pr = Process(target=read, args=(q,)) # 启动子进程pw,写入: pw.start() # 启动子进程pr,读取: pr.start() # 等待pw结束: pw.join() # pr进程里是死循环,无法等待其结束,只能强行终止: pr.terminate()if __name__ == '__main__': ipc_queue()

运行结果如下所示:

Process to write: 24992Put A to queue...Process to read: 22836Get A from queue.Put B to queue...Get B from queue.Put C to queue...Get C from queue.

Python基础教程主要是介绍进程和线程的概念,然后就是介绍多进程及其实现方式,在下一篇文章会介绍多线程的实现,以及两种方式应该如何选择。

转载地址:https://blog.csdn.net/weixin_40004659/article/details/111113368 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:打包的时候不把配置文件加进去_修改UE4 打包后的自定义用户配置
下一篇:systemctl start named失败的解决方法_anaconda安装失败解决方法

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2024年04月22日 19时26分22秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章