概念

  • 包 是一个 包含多个模块 的 特殊目录
  • 目录下有一个 特殊的文件 __init__.py
  • 包名的 命名方式 和变量名一致,小写字母 + _

    __init__.py的作用:

    要在外界使用 中的模块,需要在__init__.py中指定对外界提供的模块列表

    # 从 当前目录 导入 模块列表
    from . import send_message
    from . import receive_message

作用

使用import能一次性导入包中所有的模块

多任务

写在最前,Python中多任务有三个,进程,线程,协程。

协程在这里没写,要看协程看移动硬盘里的Python2018

概念

多任务是指在同一时间内执行多个任务,例如: 现在电脑安装的操作系统都是多任务操作系统,可以同时运行着多个软件。

执行方式

  • 并发:在一段时间内交替去执行任务。
  • 并行:对于多核cpu处理多任务,操作系统会给cpu的每个内核安排一个执行的软件,多个内核是真正的一起执行软件。这里需要注意多核cpu是并行的执行多任务,始终有多个软件一起执行

进程

概念

一个正在运行的程序或者软件就是一个进程,它是操作系统进行资源分配的基本单位,也就是说每启动一个进程,操作系统都会给其分配一定的运行资源(内存资源)保证进程的运行。

注意:

一个程序运行后至少有一个进程,一个进程默认有一个线程,进程里面可以创建多个线程,线程是依附在进程里面的,没有进程就没有线程执行任务的是线程

使用方式
  1. 导入包

    #导入进程包
    import multiprocessing
  2. 填入参数(Process是一个类)

    Process([group [, target [, name [, args [, kwargs]]]]])

    • group:指定进程组,目前只能使用None
    • target:执行的目标任务名
    • name:进程名字
    • args:以元组方式给执行任务传参
    • kwargs:以字典方式给执行任务传参

    Process创建的实例对象的常用方法:

    • start():启动子进程实例(创建子进程)
    • join():等待子进程执行结束
    • terminate():不管任务是否完成,立即终止子进程

    Process创建的实例对象的常用属性:

    name:当前进程的别名,默认为Process-N,N为从1开始递增的整数

  3. start启动执行任务

    import multiprocessing
    import time
    
    
    # 跳舞任务
    def dance():
        for i in range(5):
            print("跳舞中...")
            time.sleep(0.2)
    
    
    # 唱歌任务
    def sing():
        for i in range(5):
            print("唱歌中...")
            time.sleep(0.2)
    
    if __name__ == '__main__':
        # 创建跳舞的子进程
        # group: 表示进程组,目前只能使用None
        # target: 表示执行的目标任务名(函数名、方法名)
        # name: 进程名称, 默认是Process-1, .....
        dance_process = multiprocessing.Process(target=dance, name="myprocess1")
        sing_process = multiprocessing.Process(target=sing)
    
        # 启动子进程执行对应的任务
        dance_process.start()
        sing_process.start()

获取进程编号

获取进程编号的目的是验证主进程和子进程的关系,可以得知子进程是由那个主进程创建出来的。

获取进程编号的两种操作

  • 获取当前进程编号os.getpid()
  • 获取当前父进程编号os.getppid()

关于传参数

用args(元组)传参时,只传一个参数时,注意要加一个逗号,否则会认为是一个普通的参数。使用的是顺序传参

例如:(3,)

用kwargs(字典)传参时,注意用大括号。key一定要与参数列表中的一样。使用的是关键字传参

注意点
  • 进程之间不共线全局变量
  • 主进程会等待所有的子进程执行结束后再结束

线程

概念

线程是进程中执行代码的一个分支,每个执行分支(线程)要想工作执行代码需要cpu进行调度 ,也就是说线程是cpu调度的基本单位,每个进程至少都有一个线程,而这个线程就是我们通常说的主线程。

使用方式
  1. 导入threading模块

    #导入线程模块
    import threading
  2. 创建子线程对象

    Thread([group [, target [, name [, args [, kwargs]]]]])

    • group: 线程组,目前只能使用None
    • target: 执行的目标任务名
    • args: 以元组的方式给执行任务传参
    • kwargs: 以字典方式给执行任务传参
    • name: 线程名,一般不用设置
    • daemon:是否守护主线程
  3. 启动线程执行相应的任务

    启动线程使用start方法

    import threading
    import time
    
    # 唱歌任务
    def sing():
        # 扩展: 获取当前线程
        # print("sing当前执行的线程为:", threading.current_thread())
        for i in range(3):
            print("正在唱歌...%d" % i)
            time.sleep(1)
    
    # 跳舞任务
    def dance():
        # 扩展: 获取当前线程
        # print("dance当前执行的线程为:", threading.current_thread())
        for i in range(3):
            print("正在跳舞...%d" % i)
            time.sleep(1)
    
    
    if __name__ == '__main__':
        # 扩展: 获取当前线程
        # print("当前执行的线程为:", threading.current_thread())
        # 创建唱歌的线程
        # target: 线程执行的函数名
        sing_thread = threading.Thread(target=sing)
    
        # 创建跳舞的线程
        dance_thread = threading.Thread(target=dance)
    
        # 开启线程
        sing_thread.start()
        dance_thread.start()
    传参数跟进程一样
注意点
  • 线程之间执行是无序的
  • 主线程会等待所有的子线程执行结束再结束
  • 线程之间共享全局变量
  • 线程之间共享全局变量数据出现错误问题

互斥锁

概念

互斥锁: 对共享数据进行锁定,保证同一时刻只能有一个线程去操作。

注意:

  • 互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放后,其它等待的线程再去抢这个锁。
使用方式

threading模块中定义了Lock变量,这个变量本质上是一个函数,通过调用这个函数可以获取一把互斥锁。

互斥锁使用步骤:

# 创建锁
mutex = threading.Lock()

# 上锁
mutex.acquire()

...这里编写代码能保证同一时刻只能有一个线程去操作, 对共享数据进行锁定...

# 释放锁
mutex.release()

注意点:

  • acquire和release方法之间的代码同一时刻只能有一个线程去操作
  • 如果在调用acquire方法的时候 其他线程已经使用了这个互斥锁,那么此时acquire方法会堵塞,直到这个互斥锁释放后才能再次上锁。

死锁

概念

死锁: 一直等待对方释放锁的情景就是死锁

死锁的结果

  • 会造成应用程序的停止响应,不能再处理其它任务了。

闭包

定义

在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包

构成

通过闭包的定义,我们可以得知闭包的形成条件:

  1. 在函数嵌套(函数里面再定义函数)的前提下
  2. 内部函数使用了外部函数的变量(还包括外部函数的参数)
  3. 外部函数返回了内部函数

DEMO

# 定义一个外部函数
def func_out(num1):
    # 定义一个内部函数
    def func_inner(num2):
        # 内部函数使用了外部函数的变量(num1)
        result = num1 + num2
        print("结果是:", result)
    # 外部函数返回了内部函数,这里返回的内部函数就是闭包
    return func_inner

# 创建闭包实例    
f = func_out(1)
# 执行闭包
f(2)
f(3)

运行结果:

结果是: 3
结果是: 4

闭包执行结果的说明:

通过上面的输出结果可以看出闭包保存了外部函数内的变量num1,每次执行闭包都是在num1 = 1 基础上进行计算。

作用

  • 闭包可以保存外部函数内的变量,不会随着外部函数调用完而销毁。

注意点:

  • 由于闭包引用了外部函数的变量,则外部函数的变量没有及时释放,消耗内存。

修改外部变量的值

使用nonlocal关键字

# 定义一个外部函数
def func_out(num1):

    # 定义一个内部函数
    def func_inner(num2):
        # 这里本意想要修改外部num1的值,实际上是在内部函数定义了一个局部变量num1
        nonlocal num1  # 告诉解释器,此处使用的是 外部变量a
        # 修改外部变量num1
        num1 = 10
        # 内部函数使用了外部函数的变量(num1)
        result = num1 + num2
        print("结果是:", result)

    print(num1)
    func_inner(1)
    print(num1)

    # 外部函数返回了内部函数,这里返回的内部函数就是闭包
    return func_inner

# 创建闭包实例
f = func_out(1)
# 执行闭包
f(2)

小结

  1. 当返回的内部函数使用了外部函数的变量就形成了闭包
  2. 闭包可以对外部函数的变量进行保存
  3. 实现闭包的标准格式:

     # 外部函数
     def test1(a):
         b = 10
         # 内部函数
         def test2():
             # 内部函数使用了外部函数的变量或者参数
             print(a, b)
         # 返回内部函数, 这里返回的内部函数就是闭包实例
         return test2
每个闭包都是独立的