Python学习笔记--并发编程介绍(简单案例)

Python内置了多个并发编程模块,可以实现程序的并发设计。要掌握并发编程方法,首先要理解什么是并发编程,利用其并发机制来同时或交替地运行多个程序任务;其次在进行程序的并发设计前,需要先将任务分解成多个子任务,然后将每个子任务指派给一个进程或者线程来进行处理。对任务进行分解,可以按各种维度来进行划分,如读/写维度和功能维度。

Python提供了一个内置模块multiprocessing,multiprocessing 就是多进程的意思。利用此模块可以进行多进程的并发编程。一个实例代码如下:

'''
多进程案例
'''

from multiprocessing import Process
import time

def task(task_type):
    '''
    :param task_type :表示任务的类型,值为偶数时表示唱歌的任务,奇数表示跳舞的任务
    :return: void ,无返回值
    '''
    if task_type % 2 == 0:
        # 唱歌5首
        for i in range(1,6):
            print(f'唱第{i}首歌。')
        # 如果执行唱歌任务,休眠2秒钟
        print('\n执行唱歌任务完毕!\n')
        time.sleep(2)
    else:
        # 跳舞5支
        for i in range(1,6):
            print(f'跳第{i}支舞。')
        
        # 如果执行唱歌任务,休眠2秒钟
        print('\n执行跳舞任务完毕!\n')
        time.sleep(2)
    
if __name__ == '__main__':
    # 定义列表变量child_processes ,保存所有的子进程对象
    child_processes = []

    for index in range(2):
        '''
        通过Process类的构造函数创建子对象,一个子进程对象执行一类任务
        args 是一个元组类型,传递的是target中的函数所对应的位置参数
        例如这里的index 对应的是task()函数中的task_type 参数
        '''
        child_process = Process(target=task,args=(index,))
        child_processes.append(child_process)

        '''
        启动子进程,使用start()方法启动子进程时,会在内部调用子进程对象的run()方法
        在run()方法内部,执行的时构造函数中target制定的函数
        '''
        child_process.start()

        # 输出子进程的常用属性:name,pid,daemon
        print('子进程的名字:{},pid:{},daemon:{}'.format(child_process.name,child_process.pid,child_process.daemon))

    # 在列表推导式中逐一等待子进程结束
    [child_process.join() for child_process in child_processes]

    '''
    上述代码中使用了JOIN方法来等待子进程结束,等待的所有子进程结束前会一直阻塞,不会执行下一行代码
    '''
    print('\n\n所有子进程运行结束。')

运行显示如下:

上面代码运行了两个子进程,两个子进程都调用task()函数,根据子进程传给task()函数的参数不同,分别执行“唱歌”和“跳舞”动作。

上面运行的结果,会感觉是顺序执行,并没有并发执行。调整task()函数中的代码,在“唱歌”任务中,延长一些事件,会看到"打印”出来的信息,就能看出是并发了。

def task(task_type):
    '''
    :param task_type :表示任务的类型,值为偶数时表示唱歌的任务,奇数表示跳舞的任务
    :return: void ,无返回值
    '''
    if task_type % 2 == 0:
        # 唱歌5首
        for i in range(1,6):
            print(f'唱第{i}首歌。')
            time.sleep(0.5)
        # 如果执行唱歌任务,休眠2秒钟
        print('\n执行唱歌任务完毕!\n')
        time.sleep(2)
    else:
        # 跳舞5支
        for i in range(1,6):
            print(f'跳第{i}支舞。')
            time.sleep(1)
        
        # 如果执行唱歌任务,休眠2秒钟
        print('\n执行跳舞任务完毕!\n')
        time.sleep(2)

调整task()函数中代码,把“唱歌”和“跳舞”中,进行不同事件的休眠,运行程序结果如下,就看看出打印的信息“唱歌”和“跳舞”就错开了。

进程的父子关系

进程之间也存在逻辑上的父子关系,假设在A进程内部创建了多个子进程BCD等,进程A就是进程BCD等的父进程。操作系统内部并发运行了多个子进程,但进程之间不一定有逻辑上的父子关系。例如操作系统中运行了WPS,又打开了浏览器,QQ音乐,它们就不是父子进程。

系统中的进程可以有多个子进程,但每个子进程最多只能有一个父进程。通常说的多进程编程,是指在进程内部创建多个子进程,然后将分解的每一个子任务指派给一个子进程进行处理。

守护进程

守护进程(daemon)是一种特殊的进程,一直在后台运行,通常作为一种特定的服务进程出现,如系统守护进程,网络守护进程,对其它进程进行监控的进程等。守护进程有别于普通后台进程的重要一点是,守护进程不受任何终端的控制。

进程的状态

进程在系统中作为独立运行的基本单位,按照进程运行过程中的不同情况,可以定义为3种不同的进程状态。

(1)运行态:此时的进程正占用CPU,处于运行的状态。

(2)就绪态:进程已具备运行的条件,但其它进程正占用CPU,此时的进程正在等待系统分配CPU以便运行。

(3)休眠态:也称阻塞态,此时的进程不具备任何运行条件,正等待特定事件的发生。若事件发生,进程会由休眠态转为就绪态。

进程创建以后会先进入就绪态,等待系统分配CPU。例如执行上面的代码程序,系统首先创建一个这个代码程序的进程,进程创建完毕进入就绪态,一旦系统分配CPU,就会进入运行态。

简而言之,进程在执行过程中会可能发生如下4种转换:

(1)运行态--->休眠态:等待系统分配资源或特定事件发生,如等待用户从键盘输入指令。

(2)运行态--->就绪态:进程的CPU时间片已结束,或者被更高优先级进程抢占。

(3)休眠态--->就绪态:资源被分配给当前进程,或者特定事件发生。

(4)就绪态--->运行态:进程具备运行条件后,系统分配了CPU给当前进程。

进程间的通信

多个进程之间可以进行通信,可以理解为进程之间的通话。多个进程需要协同工作,共同完成子任务,从而完成整个任务,系统中提供一套通信机制是非常必要的。例如,在实际的并发程序设计找那个,父进程负责接收请求,子进程负责对请求进行处理,要让子进程接收到任务请求,父进程必须系统的通信机制对请求进行转发(实现分配任务)。

一个进程与另一个进程的通信,本质上是进行数据传输。传输的数据可能是一些任务信息,例如,父进程将任务指派给子进程;传输的数据也可能是一些信令,通过信令信息对其它进程进行管理和监控,或者是一些通知信息,通知其它进程发生了某种事情(可以开始或结束某事等)。

进程间的同步

多个进程在进行协同工作或数据共享的过程中可能会发生冲突,这时引入了一系列机制来对进程间的操作进行协调和制约。进程之间可以通过系统提供的通信机制对数据进行共享,当多个进程共享同一份资源的时候,在读/写过程中会发生数据安全或顺序混乱的问题,这时需要进行进程的同步。

这里的共享资源可以是物理设备,比如打印机,也可以是计算机中的文件、程序中的变量和数据等。以打印机为例,任何时刻只能被一个进程独占,如过不进行进程同步控制,会造成文件打印错乱。

multiprocessing包和Process模块

multiprocessing包是Python中一个包(Package),包可以理解为模块的集合。Python中的multiprocessing包中提供了一个叫Process的模块,通过Process模块可以进行进程的创建与管理。使用multiprocessing包中的Process模块,需要进行导入:

from multiprocessing import Process

Process模块是一个类,封装了进程操作的常用方法和属性,其构造函数如下:

Process(group=None,target=None,name=None,args=(),kwargs={},daemon=None)

参数说明:

(1)group参数未使用,值始终为None。

(2)target参数传递的是子进程执行的任务,通常是以函数名的形式进行传递

(3)name参数传递的是子进程的名称。

(4)args参数是一个元组,传递的是target中的函数名所对应的位置参数。

(5)kwargs参数是一个字典,传递的是target中的函数所对应的关键字函数。

(6)daemon参数默认值为None,设置为True时表示以守护进程的方式运行,子进程以守护进程运行时,不再受父进程的控制,必须在Process对象的start()方法之前调用。

Process模块常用属性:

(1)name:存储进程的名称。

(2)pid:存储进程的ID,这个ID是系统分配的唯一的数字编号。

(3)daemon:布尔类型,True表示当前进程以守护进程的方式运行。

Process模块常用方法:

(1)Process.start() :启动一个子进程,并调用该子进程的run()方法,每个Process对象最多只调用一次。

(2)Process.run() :子进程启动时运行的方法,它会执行在构造函数中传递的方法(target参数)

(3)Process.terminate():强制终止子进程,不会进行任何资源的清理。

(4)Process.jion(timeout=None) :父进程等待子进程结束,子进程结束后,父进程会回收子进程的系统资源。timeout表示超时事件,单位为秒。默认值为None,表示该方法会一直阻塞,直到调用join()方法的进程终止。timeout如果是一个正数,表示最多阻塞多少秒。

(5)Process.is_alive() :返回Process对象是否处于存活状态,返回值为布尔类型。实际上,Process对像从start()方法返回到子进程终止之前,该进程对象都处于存活状态。

以上几个方法只能由创建的子进程的对象调用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值