首页 > 系统相关 >python语言基础(七)--多进程多线程

python语言基础(七)--多进程多线程

时间:2024-09-06 13:23:15浏览次数:13  
标签:__ main name python print -- 线程 进程 多线程

多进程,多线程

1、多任务

概述

多个任务同时执行

目的

节约资源,充分利用CPU资源,提高效率

表现形式

并发:
    针对于单核CPU来讲的, 如果有多个任务同时请求执行, 但是同一瞬间CPU只能执行1个(任务), 于是就安排它们交替执行.
    因为时间间隔非常短(CPU执行速度太快了), 我们看起来好像是同时执行的, 其实不是.
并行:
    针对于多核CPU来讲的. 多个任务可以同时执行.

2、多进程

(一)、概述

多个任务同时进行

(二)、多进程代码

import multiprocessing, time

# 需求: 模拟一遍写代码, 一遍听音乐.

# 1. 定义函数, 表示: 写代码.
def coding():
    for i in range(1, 21):
        # 为了让效果更明显, 加入: 休眠线程.
        time.sleep(0.01)
        print(f"正在写代码... {i}", end='\n')


# 2. 定义函数, 表示: 听音乐.
def music():
    for i in range(1, 21):
        # 为了让效果更明显, 加入: 休眠线程.
        time.sleep(0.01)
        print(f"正在听音乐------ {i}")


# 3. 在main函数中测试.
if __name__ == '__main__':
    # 4. 创建两个进程对象, 分别关联上述的两个函数.
    p1 = multiprocessing.Process(target=coding)
    p2 = multiprocessing.Process(target=music)

    # 5. 启动进程.
    p1.start()
    p2.start()

(三)、带参数的多进程

参数解释

进程的参数解释, 即: multiprocessing模块的 Process类的 的参数:
    target: 用于关联 进程要执行的任务的.
    name:   进程名, 默认是: Process-1, Process-2,......Process-n, 可以手动修改, 一般不改.
    args:   可以通过 元组 的形式传递参数, 实参的个数 及 对应的数据类型 要和 形参的个数及类型 一致.
    kwargs: 可以通过 字典 的形式传递参数, 实参的个数 要和 形参的个数 一致.

代码

import multiprocessing, time


# 需求: 使用多进程来模拟小明一边写num行代码, 一边听count首音乐.
# 1. 小明写num行代码
def code(name, num):
    for i in range(1, num):
        # 为了让效果更明显, 加入: 休眠线程.
        time.sleep(0.01)
        print(f"{name} 正在写第 {i} 行代码... ")


# 2. 小明听count首音乐
def music(name, count):
    for i in range(1, count):
        # 为了让效果更明显, 加入: 休眠线程.
        time.sleep(0.01)
        print(f"{name} 正在听第 {i} 首音乐------ ")


# 3. 在main函数中编写测试代码.
if __name__ == '__main__':
    # 7. 看看我在不同的地方, 有什么结果.
    for i in range(200):
        time.sleep(0.01)
        print(f'去码头整点薯条 --- 666! {i}')

    # 4. 创建进程对象.
    p1 = multiprocessing.Process(target=code, args=("小明", 20), name='QQ进程')
    # print(f'p1进程的名字: {p1.name}')
    p2 = multiprocessing.Process(target=music, kwargs={'count': 20, 'name': '小明'}, name='微信进程')
    # print(f'p2进程的名字: {p2.name}')

    # 5. 启动进程.
    p1.start()
    p2.start()

    # 6. 看看我在不同的地方, 有什么结果.
    # for i in range(200):
    #     time.sleep(0.01)
    #     print(f'去码头整点薯条 --- 666! {i}')

注意:6处这里的代码会与p1,p2抢占资源,而7处的代码则不会。

因为在启动进程的同时,会在内存空间中自定义出一个额外的栈,也就是说p1.start()、p2.start()这两步,分别创建了两个自定义栈01、02,此时三个栈(01,02,main)同时运行,抢占资源。而7处的代码因为在创建自定义栈之前,所以不会出现抢占情况。

(四)、获取进程的id

概述

在操作系统中, 每个进程都有自己唯一的ID, 且当前进程被终止时, 该ID会被回收, 即: ID是可以重复使用的

目的

  • 查找子进程是由那个父进程创建的, 即: 找子进程和父进程的ID.

  • 方便我们维护进程, 例如: kill -9 pid值 可以强制杀死进程.

获取进程id方式

获取当前进程的ID:
    方式1: os模块的 getpid()方法
    方式2: multiprocessing模块的 pid属性
获取当前进程的父进程的pid:
    os模块的 getppid()方法   parent process: 父进程

例如

# 导包
import multiprocessing, time, os


# 案例: 小明一边敲着第n行代码, 一边听着第n首音乐.
# 1. 定义函数, 表示: 敲代码.
def code(name, num):
    for i in range(1, num + 1):
        print(f'{name} 正在敲第 {i} 行代码...')
        time.sleep(0.1)
        # multiprocessing模块的current_process()函数: 获取当前进程对象
        print(f'当前进程(p1)的id: {os.getpid()}, {multiprocessing.current_process().pid}, 父进程的id为: {os.getppid()}')


# 2. 定义函数, 表示: 听音乐
def music(name, count):
    for i in range(1, count + 1):
        print(f'{name} 正在听第 {i} 首音乐.........')
        time.sleep(0.1)
        print(f'当前进程(p2)的id: {os.getpid()}, {multiprocessing.current_process().pid}, 父进程的id为: {os.getppid()}')


# 在main中测试.
if __name__ == '__main__':
    # 3. 创建进程对象.
    # Process类的参数: target: 关联的函数名, name: 当前进程的名字, args:元组的形式传参. kwargs: 字典的形式传参.
    p1 = multiprocessing.Process(name='Process_QQ', target=code, args=('乔峰', 10))
    p2 = multiprocessing.Process(name='Process_Wechat', target=music, kwargs={'count': 10, 'name': '虚竹'})

    # print(f"p1进程的名字: {p1.name}")
    # print(f"p2进程的名字: {p2.name}")

    # 4. 启动进程.
    p1.start()
    p2.start()

    print(f'当前进程(main)的id: {os.getpid()}, {multiprocessing.current_process().pid}, 父进程的id为: {os.getppid()}')

(五)、进程间数据相互隔离

概述

  • 进程之间数据是相互隔离的. 例如: 微信(进程) 和 QQ(进程)之间, 数据就是相互隔离的.

  • 默认情况下, 主进程会等待它所有的子进程 执行结束再结束.

细节

多进程之间, 针对于 main进程的(外部资源), 每个子进程都会拷贝一份, 进行执行.

例如

import multiprocessing, time

# 需求: 定义1个列表, 然后定义两个函数, 分别往列表中添加数据, 获取数据. 之后用两个进程关联这个两个函数, 启动进程并观察结果.
# 1. 定义全局变量 my_list
my_list = []
print('我是main外资源, 看看我执行了几遍!')

# 2. 定义函数, 实现往列表中添加数据.
def write_data():
    for i in range(1, 6):
        # 具体的添加元素的动作
        my_list.append(i)
        # 打印添加动作.
        print(f'添加 {i} 成功!')
    # 细节: 添加数据完毕后, 打印结果.
    print(f'write_data函数: {my_list}')

# 3. 定义函数, 实现从列表中获取数据.
def read_data():
    # 休眠 3 秒, 确保 write_data函数执行完毕.
    time.sleep(3)
    # 打印结果.
    print(f'read_data函数: {my_list}')


# 在main中测试
if __name__ == '__main__':
    # 4. 创建进程对象.
    p1 = multiprocessing.Process(target=write_data)
    p2 = multiprocessing.Process(target=read_data)

    # 5. 启动进程
    p1.start()
    p2.start()

    # print('我是main内资源, 看看我执行了几遍!')

(六)、主进程与子进程

(1)、默认情况下主进程会等待子进程结束再结束
问: 如果要实现主进程结束时, 子进程也同步结束, 怎么办?
答:
    1. 设置子进程为 守护进程.     类似于: 骑士(守护) 和 公主(非守护)
    2. 手动关闭子进程.

代码

import multiprocessing, time


# 需求: 设置子进程执行3秒, 主进程执行1秒, 观察效果.
# 1. 定义方法, 用于关联子进程的.
def my_method():
    for i in range(10):
        print(f'工作中... {i}')
        time.sleep(0.3)  # 总休眠时间 = 0.3 * 10 = 3秒


# 2. 在main方法中测试.
if __name__ == '__main__':
    # 3. 创建子进程对象, 并启动.
    p1 = multiprocessing.Process(target=my_method)
    p1.start()  # 3秒后结束.

    # 4. 主进程执行1秒后结束.
    time.sleep(1)

    # 5. 打印主进程的结束提示.
    print('主进程 main 执行结束!')
(2)、子进程随着主进程关闭而关闭

格式

  • 进程名称.daemon = True # 设置子进程p1为守护进程, 非守护进程是: main进程, 所以: 当main进程关闭的时候, 它的守护进程也会关闭.

  • 进程名称.terminate() # 会导致子进程变成僵尸进程, 即: 不会立即释放资源.

例如

import multiprocessing, time


# 需求: 设置子进程执行3秒, 主进程执行1秒, 观察效果.
# 1. 定义方法, 用于关联子进程的.
def my_method():
    for i in range(10):
        print(f'工作中... {i}')
        time.sleep(0.3)  # 总休眠时间 = 0.3 * 10 = 3秒


# 2. 在main方法中测试.
if __name__ == '__main__':
    # 3. 创建子进程对象, 并启动.
    p1 = multiprocessing.Process(target=my_method)
    # 方式1: 设置子进程p1为守护进程, 非守护进程是: main进程, 所以: 当main进程关闭的时候, 它的守护进程也会关闭.
    p1.daemon = True
    p1.start()  # 3秒后结束.

    # 4. 主进程执行1秒后结束.
    time.sleep(1)

    # 方式2: 手动关闭子进程.
    # 会导致子进程变成僵尸进程, 即: 不会立即释放资源.
    # 而是交由init进程接管(充当新的父进程), 在合适的时机释放资源.
    # p1.terminate()      # 不推荐使用.

    # 5. 打印主进程的结束提示.
    print('主进程 main 执行结束!')

3、多线程

(一)、概述

线程是CPU调度资源的最基本单位, 进程是CPU分配资源的基本单位.

进程 = 可执行程序, 文件.
    即: *.exe = 进程,  微信, QQ都是进程.
线程 = 进程的执行路径, 执行单元.
    微信这个进程, 可以实现: 和张三聊聊天, 和李四聊天, 查看朋友圈, 微信支付...

(二)、区别

  • 关系区别:
    线程是依赖进程的, 且1个进程至少会有1个线程.

  • 特点区别:

    • 进程间数据是相互隔离的, 线程间数据是可以共享的.
    • 线程间同时操作共享数据, 可能引发安全问题, 需要用到 互斥锁 的思路解决
    • 进程的资源开销要比 线程的资源开销大.
    • 多进程程序 比 单进程多线程程序要更加的稳定.
  • 优缺点:
    进程: 可以实现多核操作, 资源开销较大.
    线程: 不能使用多核, 资源开销相对较小.

大白话:

1. 线程是依赖进程的.
2. 进程数据隔离, 线程数据共享.
3. 进程资源开销比线程资源开销大, 所以相对更稳定.
4. 无论是多线程 还是多进程, 都可以实现 多任务, 目的都是: 充分利用CPU资源, 提高程序的执行效率.

(三)、多线程代码

# 导包
import threading, time

# 1.定义函数, 表示: 敲代码.
def coding():
    for i in range(10):
        print(f"正在敲代码... {i}")
        time.sleep(0.1)


# 2.定义函数, 表示: 听音乐
def music():
    for i in range(10):
        print(f"正在听音乐... {i}")
        time.sleep(0.1)

# 在main中测试.
if __name__ == '__main__':
    # 3. 创建线程对象, 分别关联上述的两个函数.
    t1 = threading.Thread(target=coding)
    t2 = threading.Thread(target=music)

    # 4. 启动线程.
    t1.start()
    t2.start()

(四)、带参数的多进程

参数解释

Thread类(线程类)中的参数 和 Process类(进程类)的参数几乎一致:
target: 关联目标函数的.
name:   线程名(Thread-1, Thread-2...) 或者 进程名(Process-1, Process-2...).
args:   以 元组的 形式传参, 个数及对应的类型都要一致.
kwargs: 以 字典的 形式传参, 个数要一致.

代码

# 导包
# from threading import Thread
import threading, time


# 1. 定义函数, 实现: 小明正在敲第n行代码.
def coding(name, num):
    for i in range(1, num + 1):
        print(f'{name} 正在敲第 {i} 行代码...')
        time.sleep(0.1)


# 2. 定义函数, 实现: 小明正在听第n首音乐
def music(name, count):
    for i in range(1, count + 1):
        print(f'{name} 正在听第 {i} 首音乐......')
        time.sleep(0.1)


# 在main中测试
if __name__ == '__main__':
    # 3. 创建线程对象.
    t1 = threading.Thread(name='杨过', target=coding, args=('乔峰', 10))
    t2 = threading.Thread(name='大雕', target=music, kwargs={'count': 10, 'name': '慕容复'})
    # print(f't1线程的名字: {t1.name}')
    # print(f't2线程的名字: {t2.name}')

    # 4. 启动线程.
    t1.start()
    t2.start()

(五)、线程执行具有随机性

细节

1. 多线程的执行具有 随机性(无序性), 其实就是在抢CPU的过程, 谁抢到, 谁执行.
2. 默认情况下: 主线程会等待子线程执行结束再结束.
3. 线程之间 会共享当前进程的 资源.
4. 多线程环境 并发 操作共享资源, 有可能引发安全问题, 需要通过 线程同步(加锁) 的思想来解决.

资源分配

1. 均分时间片, 即: 每个进程(线程)占用CPU的时间都是 相等的.
2. 抢占式调度, 谁抢到, 谁执行.  Python用的是这种.

例如

# 导包
import threading, time


# 需求: 创建多个线程, 多次运行, 观察歌词线程的执行顺序.
# 1. 定义函数, 获取线程, 并打印.
def get_info():
    # 休眠.
    time.sleep(0.5)

    # 获取当前的线程对象, 并打印.
    cur_thread = threading.current_thread()
    print(f'当前线程是: {cur_thread}')


# 在main中测试
if __name__ == '__main__':
    # 2. 创建多个线程对象.
    for i in range(10):
        th = threading.Thread(target=get_info)
        # 3. 启动线程即可.
        th.start()

(六)、主线程与子线程

QA

问: 如何实现子线程随着主线程的结束而结束?
答:设置 子线程为 守护线程.

例如

import threading, time


# 1. 定义函数, 执行: 3秒.
def coding():
    for i in range(10):
        print(f'coding... {i}')
        time.sleep(0.3)     # 总休眠时间 = 0.3 * 10 = 3秒


# 在main中测试
if __name__ == '__main__':
    # 2. 创建线程对象.
    # 方式1: 设置th线程为: 守护线程.
    # th = threading.Thread(target=coding, daemon=True) # 推荐使用.

    # 方式2: setDaemon()函数实现.
    th = threading.Thread(target=coding)
    th.setDaemon(True)  # 函数已过时, 推荐使用方式1.

    # 3. 启动线程.
    th.start()

    # 4. 设置主线程(main线程), 执行1秒就关闭.
    time.sleep(1)

    # 5. 提示即可.
    print('主线程(main)执行结束了!')

(七)、共享全局变量

与进程不同,同一个进程内的线程共享全局变量。

# 导包
import threading, time

# 1. 定义全局变量
my_list = []

# 2. 定义函数, 往列表中: 添加数据.
def write_data():
    for i in range(1, 6):
        my_list.append(i)
        print(f'添加 {i} 成功!')
    print(f'write_data函数: {my_list}')

# 3. 定义函数, 从列表中, 读取数据.
def read_data():
    # 为了效果更明显, 我们加入休眠线程, 确保 添加动作 执行完毕了.
    time.sleep(2)
    print(f'read_data函数: {my_list}')

# 在main中测试.
if __name__ == '__main__':
    # 4. 创建线程对象.
    t1 = threading.Thread(target=write_data)
    t2 = threading.Thread(target=read_data)

    # 5. 启动线程.
    t1.start()
    t2.start()

(八)、多线程引发的安全问题以及解决

(1)、安全问题

描述

问题描述: 两个线程分别对全局变量累加100W次, 我们想看到100W 和 200W的结果, 但是结果和我们想的不一样.
产生原因: 多线程 并发 操作共享(全局)变量, 引发 安全问题, 具体如下:
    正常情况:
        1. 假设 全局变量 global_num = 0
        2. 此时 线程t1抢到了资源, 执行累加(一次)动作, 累加后: global_num = 1
        3. 假设 线程t2抢到了资源, 执行累加(一次)动作, 累加后: global_num = 2
        4. 这个是正常的情况.
    非正常情况:
        1. 假设 全局变量 global_num = 0
        2. 此时 线程t1抢到了资源, 但是还没有来得及执行累加动作时, 被t2线程抢走了资源.
        3. 此时 线程t2读取到的 global_num = 0
        4. 此时就会出现 线程t1累加1次, global_num = 1, 线程t2累加1次, global_num = 1
        5. 综上所述, t1和t2线程一共累加了2次, 但是 global_num的值 只加了1
        6. 之所以会有这样的情况, 原因是: 1个线程在执行某1个完整动作期间, 可以被别的前程抢走资源, 就有可能引发安全问题.

例如

# 导包
import threading, time

# 1. 定义全局变量.
global_num = 0


# 2. 定义函数1, 对全局变量 global_num累加100W次.
def get_sum1():
    # 声明变量为 全局变量.
    global global_num
    # 具体的累加动作.
    for i in range(1000000):
        global_num += 1
    # 累加完毕后, 打印结果.
    print(f'get_sum1函数执行完毕, global_num = {global_num}')


# 3. 定义函数2, 对全局变量 global_num累加100W次.
def get_sum2():
    # 声明变量为 全局变量.
    global global_num
    # 具体的累加动作.
    for i in range(1000000):
        global_num += 1
    # 累加完毕后, 打印结果.
    print(f'get_sum2函数执行完毕, global_num = {global_num}')


# main函数, 测试
if __name__ == '__main__':
    # 4. 创建线程对象.
    t1 = threading.Thread(target=get_sum1)
    t2 = threading.Thread(target=get_sum2)

    # 5. 启动线程.
    t1.start()
    t2.start()
(2)、问题解决

解决方案

解决方案:
    采用 线程同步 的思想解决, 即: 加锁.

线程同步:
    概述:
        用于解决多线程 并发 操作共享变量的安全问题的, 保证同一时刻只有1个线程操作共享变量.
    使用步骤:
        1. 创建锁.
        2. 在合适的地方: 加锁.
        3. 在合适的地方: 释放锁.
    细节:
        1. 必须使用同一把锁, 否则可能出现锁不住的情况.
        2. 必须在合适的时机释放锁, 否则可能出现死锁的情况.

例如

# 导包
import threading, time

# 1. 定义全局变量.
global_num = 0

# 创建锁.
mutex = threading.Lock()        # 互斥锁.
# mutex2 = threading.Lock()        # 互斥锁.

# 2. 定义函数1, 对全局变量 global_num累加100W次.
def get_sum1():
    # 加锁.
    mutex.acquire()
    # 声明变量为 全局变量.
    global global_num
    # 具体的累加动作.
    for i in range(1000000):
        global_num += 1
    # 解锁
    mutex.release()
    # 累加完毕后, 打印结果.
    print(f'get_sum1函数执行完毕, global_num = {global_num}')


# 3. 定义函数2, 对全局变量 global_num累加100W次.
def get_sum2():
    # 加锁.
    # mutex2.acquire()
    mutex.acquire()
    # 声明变量为 全局变量.
    global global_num
    # 具体的累加动作.
    for i in range(1000000):
        global_num += 1
    # 解锁
    # mutex2.release()
    mutex.release()
    # 累加完毕后, 打印结果.
    print(f'get_sum2函数执行完毕, global_num = {global_num}')

# main函数, 测试
if __name__ == '__main__':
    # 4. 创建线程对象.
    t1 = threading.Thread(target=get_sum1)
    t2 = threading.Thread(target=get_sum2)

    # 5. 启动线程.
    t1.start()
    t2.start()

标签:__,main,name,python,print,--,线程,进程,多线程
From: https://blog.csdn.net/egglikeFe/article/details/141957310

相关文章

  • 20240906_142048 c语言 认识c语言
    C语言是一种广泛使用的编程语言,它以其高效、灵活和接近硬件的特性而闻名。对于零基础的学生来说,学习C语言是一个很好的起点,因为它不仅能帮助你理解计算机程序的基本结构和概念,还能为学习更高级的编程语言(如C++、Java、Python等)打下坚实的基础。下面我将简要介绍C语言的一些基本概念......
  • 蔚来发布新财报,亏损收窄,营收同比增长98.9%!
    KlipC报道:9月5日,蔚来发布2024年二季度财报,财报显示,营收174.5亿元,同比增长98.9%,环比增长76.1%;交付量5.74万台,同比增长143.9%,环比增长90.9%;营收和交付量双创新高。整车毛利率12.2%,同比提升6个百分点,环比提升3个百分点;净亏损50.46亿元,同比收窄16.7%,环比收窄2.7%。蔚来CEO李斌表示,“......
  • Python全网最全基础课程笔记(五)——选择结构+Python新特性Match
    本专栏系列为Pythong基础系列,每篇内容非常全面,包含全网各个知识点,非常长,请耐心看完。每天都会更新新的内容,搜罗全网资源以及自己在学习和工作过程中的一些总结,可以说是非常详细和全面。以至于为什么要写的这么详细:自己也是学过Python的,很多新手只是简单的过一篇语法,其实对......
  • 微信聊天记录找回,守护你的珍贵记忆
    在这个信息爆炸的时代,微信已成为我们日常生活中不可或缺的一部分。它不仅连接着我们的亲朋好友,更承载着无数珍贵的回忆和重要的信息。然而,数据丢失的阴影始终潜伏,一旦发生,那些珍贵的对话、重要的文件、温馨的瞬间,都可能瞬间消失,留下的只有无尽的遗憾和焦急。但请记住,希望之光从......
  • 学会这三招,没人比你更快恢复聊天记录
    那些记录着生活点滴的微信对话,不仅仅是文字,更是情感的寄托和时间的见证。现代都市人在数字世界中寻找遗失记忆的缩影。它提醒我们,在享受科技带来的便利的同时,也要时刻警惕数据安全,珍惜那些通过屏幕传递的温暖与情感。我来告诉大家聊天记录不慎丢失了该如何恢复第一步打开手机......
  • 基于nodejs+vue大学校园公益回收平台设计与实现[程序+论文+开题]-计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容研究背景随着高等教育的普及与校园生活的日益丰富,大学校园内产生的可回收物品及闲置资源日益增长。然而,传统的废弃物处理方式往往侧重于简单的丢弃或低效率的回收,这......
  • 基于nodejs+vue大学校园网订餐管理系统[程序+论文+开题]-计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容研究背景随着信息技术的飞速发展和高校校园生活的日益丰富,大学生对于便捷、高效的生活服务需求日益增长。传统的校园餐饮模式往往受限于时间、地点等因素,难以满足学......
  • 基于nodejs+vue大学校园网络综合服务平台[程序+论文+开题]-计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容研究背景随着信息技术的飞速发展和高校规模的不断扩大,大学校园内的信息交流与服务需求日益多样化与复杂化。传统的管理与服务模式已难以满足师生们高效、便捷的生活......
  • 基于nodejs+vue大学新生入学帮助系统[程序+论文+开题]-计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容研究背景随着高等教育规模的不断扩大,每年有大量新生踏入大学校园,面对全新的环境、复杂的学业要求以及独立生活的挑战,他们往往感到迷茫与不安。大学不仅是知识学习的......
  • 免费赠送一个几百块的小项目!赶紧行动
    最近MarsCode在做推广活动,换句话就是拉人头,期初我也不信,抱着试一试的态度,最终拿到结果,推荐的几个小伙伴也都领到了几百块的京东卡,这个小项目跟着做就有结果,审核周期大概两三天。什么是MarsCodeIDE?MarsCodeIDE是一个云端AIIDE平台。通过内置的AI编程助手,开箱即......