Python 高级:03 多进程

一、多任务

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

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

二、进程

1. 进程的介绍
在Python程序中,想要实现多任务可以使用进程来完成,进程是实现多任务的一种方式。

2. 进程的概念
一个正在运行的程序或者软件就是一个进程,它是操作系统进行资源分配的基本单位,也就是说每启动一个进程,操作系统都会给其分配一定的运行资源(内存资源)保证进程的运行。
注意:一个程序运行后至少有一个进程,一个进程默认有一个线程,进程里面可以创建多个线程,线程是依附在进程里面的,没有进程就没有线程。

三、进程的使用

1. 代码实现

"""
Process([group[,target[.name[,args[,kwargs]]]]])
group:指定进程组,目前只能用None
target:执行的目标任务名
name:进程名
args:以元祖方式给执行任务传参
kwargs:以字典方式给执行任务传参

Process创建实例对象的常用方法:
start():启动子进程实例(创建子进程)
join():等待子进程执行结束
terminate():不管任务是否完成,立即终止子进程

Process创建的实例对象的常用属性:
name:当前进程的别名,默认为Process-N,N为从1开始递增的整数
"""
import multiprocessing
import time

def process_1():
    for i in range(5):
        print(f"开发功能{i}")
        time.sleep(0.5)

def process_2():
    for i in range(5):
        print(f"测试功能{i}")
        time.sleep(0.5)

if __name__ == '__main__':
    
    sub_process1 = multiprocessing.Process(target=process_1)
    sub_process2 = multiprocessing.Process(target=process_2)

    sub_process1.start()
    sub_process2.start()

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

import multiprocessing
import time
import os

def process_1():
    # 获取当前代码的进程对象
    p1 = multiprocessing.current_process()
    print("p1:", p1)
    # 获取当前进程(子进程)编号
    p1_pid = os.getpid()
    # 获取当前进程的父进程编号
    p1_ppid = os.getppid()
    print("p1_pid:", p1_pid)
    print("p1_ppid:", p1_ppid)
    for i in range(5):
        print(f"开发功能{i}")
        time.sleep(0.5)

def process_2():
    # 获取当前代码的进程对象
    p2 = multiprocessing.current_process()
    print("p1:", p2)
    # 获取当前进程(子进程)编号
    p2_pid = os.getpid()
    # 获取当前进程的父进程编号
    p2_ppid = os.getppid()
    print("p2_pid:", p2_pid)
    print("p2_ppid:", p2_ppid)
    for i in range(5):
        print(f"测试功能{i}")
        time.sleep(0.5)

if __name__ == '__main__':

    sub_process1 = multiprocessing.Process(target=process_1)
    sub_process2 = multiprocessing.Process(target=process_2)

    sub_process1.start()
    sub_process2.start()

3. 进程执行带有参数的任务

import multiprocessing


def func1(act, count):
    act = act
    for i in range(count):
        print(f"{act}产品{i}")


if __name__ == '__main__':
    # 创建子进程
    # 元组如果只有一个元素,那么元素后面的逗号不能省略
    # args:表示以元组的方式给执行任务传参,实际上是按照函数位置参数进行传参的
    # sub_porcess = multiprocessing.Process(target=func1, args=("研发", 3))
    # sub_porcess.start()

    # kwargs:表示以字典方式给执行任务传参,实际上是按照函数关键字传参的
    sub_process = multiprocessing.Process(target=func1, kwargs={"act":"研发", "count":3})
    sub_process.start()

4. 进程之间不共享全局变量

import multiprocessing
import time

g_list = list()


def add_data():
    for i in range(3):
        g_list.append(i)
        time.sleep(0.3)
    print("add_data:", g_list)


def read_data():
    print("read_data:", g_list)


if __name__ == '__main__':
    add_data_process = multiprocessing.Process(target=add_data)
    read_data_process = multiprocessing.Process(target=read_data)

    add_data_process.start()
    # 进程等待,主进程等待子进程(add_data_process)执行完成以后,再执行下面的代码
    add_data_process.join()
    read_data_process.start()

    print("全局变量g_list:", g_list)

5. 主进程会等待所有的子进程完成以后再退出

import multiprocessing
import time

def task():
    for i in range(3):
        print("子进程正在运行...")
        time.sleep(0.5)

if __name__ == '__main__':

    sub_process = multiprocessing.Process(target=task)
    # 1. 设置守护主进程,主进程退出子进程就销毁,停止程序运行
    # sub_process.daemon = True
    sub_process.start()

    # 主进程延时1秒
    time.sleep(1)
    # 2. 在此处销毁子进程
    sub_process.terminate()

    print("主进程挂了!")

四、线程

1. 线程的介绍
在Python中,想要实现多任务除了使用进程,还可以使用线程来完成,线程是实现多任务的另外一种方式。

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

3. 线程的作用
多线程可以完成多任务
在这里插入图片描述 在这里插入图片描述
程序启动默认会有一个主线程,程序自己创建的线程可以称为子线程,多线程可以完成多任务。
线程是python程序中实现多任务的另外一种方式,线程的执行需要cpu调度来完成。

"""
Thread([group[, target[, name[, args[, kwargs]]]]])
group:线程组,目前只能使用None
target:执行的目标任务名
args:以元组的方式给执行任务传参
kwargs:以字典方式给执行任务传参
name:线程名,一般不设置
"""
import threading
import time


def thread_1():
    for i in range(3):
        print("研发功能")
        time.sleep(0.1)


def thread_2():
    for i in range(3):
        print("测试功能")
        time.sleep(0.1)


if __name__ == '__main__':

    sub_thread1 = threading.Thread(target=thread_1)
    sub_thread2 = threading.Thread(target=thread_2)

    sub_thread1.start()
    sub_thread2.start()

4. 线程执行带有参数的任务

import threading
import time


def thread_1(act, t):
    for i in range(t):
        print(f"{act}功能{i}")
        time.sleep(0.1)


if __name__ == '__main__':

    # 获取当前执行代码的线程
    main_thread = threading.current_thread()
    print("main_thread:", main_thread)

    # sub_thread = threading.Thread(target=thread_1, args=("研发", 3))
    sub_thread = threading.Thread(target=thread_1, kwargs={"act":"研发", "t":3})
    # 打印线程名
    print(sub_thread.name)
    sub_thread.start()

5. 线程之间执行是无序的

import threading
import time


def task():
    time.sleep(1)
    print(threading.current_thread().name)


if __name__ == '__main__':

    # for i in range(10):
    #     sub_thread = threading.Thread(target=task)
    #     sub_thread.start()

    t_list = []
    for i in range(10):
        sub_thread = threading.Thread(target=task)
        t_list.append(sub_thread)
    for t in t_list:
        t.start()

线程之间执行是无序的,它是由cpu调度决定的,cpu调度哪个线程,哪个线程就先执行,没有调度的线程不能执行。
进程之间执行也是无序的,它是由操作系统调度决定的,操作系统调度哪个进程,哪个进程就先执行,没有调度的进程不能执行。

6. 主线程会等待所有的子线程执行完成以后再结束

import threading
import time


def task():
    for i in range(10):
        print("执行子线程")
        time.sleep(0.3)


if __name__ == '__main__':

    # 守护主线程有以下两种方法,主线程退出子线程销毁
    sub_thread = threading.Thread(target=task, daemon=True)
    # sub_thread.setDaemon(True)
    
    sub_thread.start()

    time.sleep(1)
    print("主线程结束")
    # 默认情况下,主线程会等待所有的子线程执行完成以后再退出

7. 线程之间共享全局变量

import threading
import time

# 定义全局变量
g_list = []


def add_data():
    for i in range(3):
        g_list.append(i)
        time.sleep(0.1)
    print("add_data:", g_list)


def read_data():
    print("read_data:", g_list)


if __name__ == '__main__':
    add_data_thread = threading.Thread(target=add_data)
    read_data_thread = threading.Thread(target=read_data)

    add_data_thread.start()
    add_data_thread.join()
    read_data_thread.start()

8. 线程之间共享全局变量数据错误问题

import threading

# 定义全局变量
g_num = 0


def add_num1():
    global g_num
    for i in range(100000):
        g_num += 1
    print("add_num1:", g_num)


def add_num2():
    global g_num
    for i in range(100000):
        g_num += 1
    print("add_num2:", g_num)


if __name__ == '__main__':
    add_num1_thread = threading.Thread(target=add_num1)
    add_num2_thread = threading.Thread(target=add_num2)

    add_num1_thread.start()
    add_num2_thread.start()

解决办法1:线程同步

import threading

# 定义全局变量
g_num = 0


def add_num1():
    global g_num
    for i in range(100000):
        g_num += 1
    print("add_num1:", g_num)


def add_num2():
    global g_num
    for i in range(100000):
        g_num += 1
    print("add_num2:", g_num)


if __name__ == '__main__':
    add_num1_thread = threading.Thread(target=add_num1)
    add_num2_thread = threading.Thread(target=add_num2)

    add_num1_thread.start()
    # 主线程等待第一个子线程执行完成以后程序再执行(线程同步)
    add_num1_thread.join()
    add_num2_thread.start()

解决办法2:互斥锁

"""
互斥锁:对共享数据进行锁定,保证同一时刻只能有一个线程去操作。threading 模块中定义了 Lock 变量,
这个变量本质上是一个函数,通过调用这个函数可以获取一把互斥锁。
注意:互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放
后,其它等待的线程再去抢这个锁。acquire 和 release 方法之间的代码同一时刻只能有一个线程去操作。
如果在调用 acquire 方法的时候,其他线程已经使用了这个互斥锁,那么此时 acquire 方法会堵塞,直到
这个互斥锁释放后才能再次上锁。
"""
import threading

# 定义全局变量
g_num = 0

# 创建互斥锁,本质上是一个函数
lock = threading.Lock()


def add_num1():
    # 操作共享数据之前上锁
    lock.acquire()
    # 声明此处加上global表示要修改全局变量的内存地址
    global g_num
    for i in range(100000):
        g_num += 1
    print("add_num1:", g_num)
    # 共享数据操作完成以后,需要释放锁
    lock.release()


def add_num2():
    # 操作共享数据之前上锁
    lock.acquire()
    # 声明此处加上global表示要修改全局变量的内存地址
    global g_num
    for i in range(100000):
        g_num += 1
    print("add_num2:", g_num)
    # 共享数据操作完成以后,需要释放锁
    lock.release()


if __name__ == '__main__':
    add_num1_thread = threading.Thread(target=add_num1)
    add_num2_thread = threading.Thread(target=add_num2)

    add_num1_thread.start()
    add_num2_thread.start()
    # 互斥锁可以解决全局变量数据错误问题,互斥锁可以保证同一时刻只有一个线程取值
    # 执行代码效率会下降,但能够保证数据的安全性

"""
死锁:一直等待对方释放锁的情景就是死锁。死锁会造成应用程序停止响应,不再处理其它任务。
"""
import threading


lock = threading.Lock()

def get_num(num):
    lock.acquire()
    l = [1, 3, 4]
    for i in range(num):
        if i >= len(l):
            print(f"索引{i}越界!")
            # 进入该逻辑return之前要释放锁
            lock.release()
            return
        print({"index": i, "value": l[i]})
    lock.release()


if __name__ == '__main__':
    for j in range(10):
        sub_thread = threading.Thread(target=get_num, args=(j,))
        sub_thread.start()

9. 进程和线程的对比
(1)线程是依附在进程里面的,没有进程就要没有线程;
(2)一个进程默认提供一条线程,进程可以创建多个线程;
在这里插入图片描述
(3)进程之间不共享全局变量;
(4)线程之间共享全局变量,但是要注意资源竞争的问题,解决办法:互斥锁或者线程同步;
(5)创建进程的资源开销要比创建线程的资源开销大;
(6)进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位;
(7)线程不能独立运行,必须依存在进程中;
(8)多进程开发比单进程多线程开发稳定性强;
(9)进程可以用多核,但资源开销大;线程资源开销小,但不能用多核;

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值