前言

进程是操作系统资源分配的基本单位,拥有独立的内存空间,稳定性高但开销大;线程是CPU调度的基本单位,共享所属进程的内存资源,轻量但安全性低。两者没有绝对的优劣,只有是否适合当前场景。


Python进程

多任务的介绍

多个任务同时执行能够充分利用CPU资源,大大提高程序执行效率

多任务的概念

概念:多任务是指在同一时间内执行多个任务(给我们的感觉)。
例如:现在电脑安装的操作系统都是多任务操作系统,可以同时运行着多个软件。
在这里插入图片描述
多任务的两种表现形式

  • 并发:在一段时间内,交替执行任务
  • 并行:在一段时间内,真正的同时一起执行多个任务

并发
在一段时间内交替去执行多个任务
对于单核cpu处理多任务,操作系统轮流让各个任务交替执行,假如:软件1执行0.01秒,切换到软件2,软件2执行0.01秒,再切换到软件3,执行0.01秒……这样反复执行下去 , 实际上每个软件都是交替执行的 . 但是,由于CPU的执行速度实在是太快了,表面上我们感觉就像这些软件都在同时执行一样 . 这里需要注意单核cpu是并发的执行多任务的
在这里插入图片描述
并行
在一段时间内真正的同时一起执行多个任务
对于多核cpu处理多任务,操作系统会给cpu的每个内核安排一个执行的任务,多个内核是真正的一起同时执行多个任务。这里需要注意多核cpu是并行的执行多任务,始终有多个任务一起执行。
在这里插入图片描述

进程的概念

进程(Process)是CPU资源分配的最小单位,它是操作系统进行资源分配和调度运行的基本单位
通俗理解:一个正在运行的程序就是一个进程 . 例如:正在运行的qq , 微信等他们都是一个进程

注意:一个程序运行后至少有一个进程

在这里插入图片描述
在这里插入图片描述

Python中多进程的基本工作方式
程序运行起来形成主进程;在主进程上创建子进程

多进程完成多任务

进程的创建步骤

  1. 导入进程工具包
 import multiprocessing
  1. 通过进程类 实例化进程 对象
子进程对象 = multiprocessing.Process() 
  1. 启动进程执行任务
进程对象.start()

通过进程类实例化创建进程对象

子进程对象 =
 multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}) 
  • group—参数未使用,值始终为None
  • target—表示调用对象,即子进程要执行的任务(回调函数入口地址)
  • args—表示以元组的形式向子任务函数传参,元组方式传参一定要和参数的顺序保持一致
  • kwargs—表示以字典的方式给子任务函数传参,字典方式传参字典中的key要和参数名保持一致
  • name—为子进程的名称

使用多进程来模拟一边编写代码,一边听音乐功能实现

import multiprocessing
import time

#编写代码任务
def coding():
    for i in range(3):
        print(f"第{i+1}次coding......")
        time.sleep(0.2)
# 听音乐任务
def music():
    for i in range(3):
        print(f"第{i+1}次music...")
        time.sleep(0.2)

if __name__ == '__main__':
    # 2.通过进程类创建进程对象,关联doding函数
    coding_process = multiprocessing.Process(target=coding)
    # 2.通过进程类创建进程对象,关联music函数
    music_process = multiprocessing.Process(target=music)
    coding_process.start()
    music_process.start()

在这里插入图片描述

import multiprocessing
import time

#编写代码任务
def coding(num, name):
    for i in range(1,num+1):
        time.sleep(0.1)
        print(f"{name}在编写第{num}次coding......")
# 听音乐任务
def music(count,name):
    for i in range(1,count+1):
        time.sleep(0.1)
        print(f"{name}在听第{count}次music...")

if __name__ == '__main__':
    # 2.通过进程类创建进程对象,关联doding函数
    coding_process = multiprocessing.Process(target=coding, args=(4,'hgq'))
    # 2.通过进程类创建进程对象,关联music函数
    music_process = multiprocessing.Process(target=music,kwargs={'name': 'ming','count': 5})
    coding_process.start()
    music_process.start()

在这里插入图片描述

元组方式传参 :元组方式传参一定要和任务函数的参数顺序保持一致。
字典方式传参:字典方式传参字典中的key一定要和任务函数的参数保持一致

获取进程编号

进程编号的作用
进程编号唯一标识一个进程, 方便管理进程。
在一个操作系统中,一个进程拥有的进程号是唯一的,进程号可以反复使用。
获取进程编号的目的是验证主进程和子进程的关系,可以得知子进程是由那个主进程创建出来的

获取进程编号的两种操作

  • 获取当前进程编号
  • 获取当前父进程编号

os.getpid()的使用

import os
print(f"当前进程编号:{os.getpid()}")

在这里插入图片描述

import multiprocessing

pid = multiprocessing.current_process().pid
print(f"当前进程号为:{pid}")

在这里插入图片描述

os.getppid()的使用

import os

pid = os.getppid()
print(f"当前进程的父进程的id为:{pid}")

在这里插入图片描述

进程的注意点

1.进程之间不共享全局变量
2. 主进程会等待所有的子进程执行结束再结束

进程间不共享全局变量

import multiprocessing
import time
# 全局变量
my_list = []
# 写入数据
def write_data():
    for i in range(3):
        my_list.append(i)
        print("add:", i)
    print("write_data", my_list)
# 读取数据
def read_data():
    print("read_data", my_list)


if __name__ == '__main__':
    # 创建写入数据进程
    write_process = multiprocessing.Process(target=write_data)
    # 创建读取数据进程
    read_process = multiprocessing.Process(target=read_data)
    # 启动进程执行相应任务
    write_process.start()
    time.sleep(1)
    read_process.start()

在这里插入图片描述
默认主进程会等待所有的子进程执行结束再结束
假如我们现在创建一个子进程,子进程执行完大概需要2秒钟,现在让主进程执行1秒钟就退出程序:

import time
import multiprocessing

def work():
    for i in range(10):
        print("工作中...")
        time.sleep(0.2)

if __name__ == '__main__':
    # 创建子进程
    workProcess = multiprocessing.Process(target=work)
    # 启动子进程
    workProcess.start()
    # 延时1秒
    time.sleep(1)
    print("主进程执行完毕")

在这里插入图片描述

不让主进程等待子进程
方法1:子进程设置守候进程
子进程设置守护主进程的目的:是主进程退出子进程销毁,不让主进程再等待子进程去执行。

import time
import multiprocessing

def work():
    for i in range(10):
        print("工作中...")
        time.sleep(0.2)

if __name__ == '__main__':
    # 创建子进程
    workProcess = multiprocessing.Process(target=work)
    #设置主进程守护
    workProcess.daemon = True
    # 启动子进程
    workProcess.start()
    # 延时1秒
    time.sleep(1)
    print("主进程执行完毕")

在这里插入图片描述
方法2:子进程自己主动的终止子进程

import time
import multiprocessing

def work():
    for i in range(10):
        print("工作中...")
        time.sleep(0.2)

if __name__ == '__main__':
    # 创建子进程
    workProcess = multiprocessing.Process(target=work)

    # 启动子进程
    workProcess.start()
    # 延时1秒
    time.sleep(1)
    # 手动结束子进程
    workProcess.terminate() #这种不建议使用,僵尸进程,不会清理资源
    print("主进程执行完毕")

在这里插入图片描述

为了保证子进程能够正常的运行,主进程会等所有的子进程执行完成以后再销毁,设置守护主进程的目的是主进程退出子进程销毁,不让主进程再等待子进程去执行。
设置守护主进程方式: 子进程对象.daemon = True
销毁子进程方式: 子进程对象.terminate()

Python线程

线程的介绍

在Python中,想要实现多任务除了使用进程,还可以使用线程来完成。

线程的概念
进程是分配资源的基本单位, 一旦创建一个进程就会分配一定的资源;
线程是cpu调度的基本单位,每个进程至少都有一个线程,而这个线程就是我们通常说的主线程。
在这里插入图片描述
在这里插入图片描述

线程用来实现多任务编程

多线程完成多任务

线程创建的步骤

  1. 导入线程模块
 import threading
  1. 通过线程类创建线程对象
线程对象 = threading.Thread(target=任务名) 
  1. 启动线程执行任务
线程对象.start()

线程类参数说明

线程对象=threading.Thread([group [, target [, name [, args [, kwargs]]]]])

group: 线程组,目前只能使用None
target: 执行的目标任务名
args: 以元组的方式给执行任务传参,元组方式传参一定要和目标任务函数参数的顺序保持一致。
kwargs: 以字典方式给执行任务传参,字典方式传参字典中的key一定要和参数的顺序保持一致
name: 线程名,一般不用设置

多线程完成多任务的代码

import time
import threading
#编写代码任务
def coding():
    for i in range(3):
        print("coding...")
        time.sleep(0.2)
#听音乐任务
def music():
    for i in range(3):
        print("music...")
        time.sleep(0.2)

if __name__ == '__main__':
    # 创建子线程
    codingThread = threading.Thread(target=coding)
    musicThread = threading.Thread(target=music)
    # 启动子线程启动任务
    codingThread.start()
    musicThread.start()

在这里插入图片描述
线程带参数的任务

import time
import threading
#编写代码任务
def coding(num, name):
    for i in range(num):
        print(f"{name}正在编写第{i+1}行代码...")
        time.sleep(0.2)
#听音乐任务
def music(count, name):
    for i in range(count):
        print(f"{name}正在听第{i+1}首歌...")
        time.sleep(0.2)

if __name__ == '__main__':
    # 创建子线程
    codingThread = threading.Thread(target=coding,args=(4,'小明'))
    musicThread = threading.Thread(target=music,kwargs={'count': 5, 'name': '小梁'})
    # 启动子线程启动任务
    codingThread.start()
    musicThread.start()

在这里插入图片描述

线程的注意点

  1. 线程之间执行是无序的
  2. 主线程会等待所有的子线程执行结束再结束
  3. 线程之间共享全局变量
  4. 线程之间共享全局变量数据出现错误问题

线程之间执行是无序的

线程之间执行是无序的,它是由cpu调度决定的 ,cpu调度哪个线程,哪个线程就执行,没有调度的线程是不能执行的。

主线程会等待所有的子线程执行结束再结束

import time
import threading
#编写代码任务
def coding(num, name):
    for i in range(num):
        print(f"{name}正在编写第{i+1}行代码...")
        time.sleep(0.2)

if __name__ == '__main__':
    # 创建子线程
    codingThread = threading.Thread(target=coding,args=(10,'小明'))
    # 启动子线程启动任务
    codingThread.start()

    time.sleep(1)
    print("主线程执行完毕")

在这里插入图片描述
主线程会等待所有的子线程执行结束再结束
子线程就销毁不再执行:
守护主线程:
守护主线程就是主线程退出子线程销毁不再执行

设置守护主线程有两种方式:

threading.Thread(target=coding, daemon=True)
线程对象.setDaemon(True)
import time
import threading
#编写代码任务
def coding(num, name):
    for i in range(num):
        print(f"{name}正在编写第{i+1}行代码...")
        time.sleep(0.2)

if __name__ == '__main__':
    # 创建子线程
    codingThread = threading.Thread(target=coding,args=(10,'小明'),daemon=True)
    # 启动子线程启动任务
    codingThread.start()

    time.sleep(1)
    print("主线程执行完毕")

在这里插入图片描述

线程之间共享全局变量

import threading
import time
# 全局变量
my_list = []
# 写入数据
def write_data():
    for i in range(3):
        my_list.append(i)
        print("add:", i)
    print("write_data", my_list)
# 读取数据
def read_data():
    print("read_data", my_list)

if __name__ == '__main__':
    # 创建写入数据线程
    write_process = threading.Thread(target=write_data)
    # 创建读取数据线程
    read_process = threading.Thread(target=read_data)
    # 启动进程执行相应任务
    write_process.start()
    time.sleep(1)
    read_process.start()

在这里插入图片描述

线程之间共享全局变量可能会导致数据出现错误问题
全局变量数据错误的解决办法:
线程同步: 保证同一时刻只能有一个线程去操作全局变量
同步: 就是协同步调,按预定的先后次序进行运行。如:你说完,我再说, 好比现实生活中的对讲机
线程同步的方式: 锁

互斥锁的使用

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

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

互斥锁的使用教程

  1. 互斥锁的创建
mutex = threading.Lock()
  1. 上锁
mutex.acquire()
  1. 释放锁
mutex.release()

使用互斥锁保证线程间的数据安全

import threading
# 全局变量
share = 0
# 创建互斥锁
mutex = threading.Lock()

def add_one():
    mutex.acquire() # 上锁
    for i in range(1000000):
        global share
        share += 1
    mutex.release() # 解锁
    print(f"add_one的函数结果为:{share}")

def add_two():
    mutex.acquire() # 上锁
    for i in range(1000000):
        global share
        share += 1
    mutex.release() # 解锁
    print(f"add_two的函数结果为:{share}")

if __name__ == '__main__':
    testOne = threading.Thread(target=add_one)
    testTwo = threading.Thread(target=add_two)
    #启动线程
    testOne.start()
    testTwo.start()

在这里插入图片描述

死锁

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

  • 使用互斥锁的时候需要注意死锁的问题,
  • 未在合适的地方注意释放锁

死锁的结果:

  • 会造成应用程序的停止响应,
  • 应用程序无法再继续往下执行了

在这里插入图片描述

进程和线程的对比

线程是依附在进程里面的,没有进程就没有线程
一个进程默认提供一条线程,进程可以创建多个线程
在这里插入图片描述

  1. 进程之间不共享全局变量
  2. 线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁
  3. 创建进程的资源开销要比创建线程的资源开销要大
  4. 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
  5. 线程不能够独立执行,必须依存在进程中
  6. Python中多进程开发比单进程多线程开发稳定性要强

进程优缺点:

  • 优点:可以用多核
  • 缺点:资源开销大

线程优缺点:

  • 优点:资源开销小
  • 缺点:不能使用多核

总结

进程和线程是Python进阶路上非常实用的技能点,掌握之后你写的脚本效率能直接提升数倍,哪怕只是用来优化自己的日常小工具,也能省下大量的时间。如果你在实操中遇到问题,或者有具体的并发场景想探讨,欢迎在评论区留言交流~

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐