Python_多任务:进程、线程、协程

news2024/11/23 16:39:07

目录

进程

实现多进程

进程池

实现进程池

线程

实现多线程

多线程的资源竞争问题

互斥锁解决资源竞争问题

死锁

协程

gevent


进程

进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。进程一般由程序、数据集合和进程控制块三部分组成。程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时所需要的数据和工作区;程序控制块包含进程的描述信息和控制信息是进程存在的唯一标志。

进程具有的特征:
动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行。
独立性:进程是系统进行资源分配和调度的一个独立单位。
结构性:进程由程序、数据和进程控制块三部分组成。

实现多进程

import multiprocessing
import time


def run1(sleep_time):
    while True:
        print("-- 1 --")
        time.sleep(sleep_time)


def run2(sleep_time):
    while True:
        print("-- 2 --")
        time.sleep(sleep_time)


def main():
    # 创建进程对象。
    # target:指定线程调用的函数名。注:等号后跟方法名不能加括号,如果加了也能执行函数但threading功能无效
    # args:指定调用函数时传递的参数。注:args是一个数组变量参数,只传一个参数时,需要在参数后面添加逗号
    p1 = multiprocessing.Process(target=run1, args=(1,))
    p2 = multiprocessing.Process(target=run2, args=(1,))

    # 启用子进程
    p1.start()
    p2.start()

    # join方法等待子进程执行结束
    p1.join()
    p2.join()
    print("子进程结束")


if __name__ == "__main__":
    main()

运行上面代码,查看任务管理器python的启动进程数。

代码中只启动了两个子进程,但是为什么有3个python进程?这是因为,python会创建一个主进程(第1个进程),当运行到p1.start()时会创建一个子进程(第2个进程),当运行到p2.start()时又会创建一个子进程(第3个进程)

进程池

进程的创建和删除是需要消耗计算机资源的,如果有大量任务需要多进程完成,则可能需要频繁的创建删除进程,这会给计算机带来较多的资源消耗。进程池的出现解决了这个问题,它的原理是创建适当的进程放入进程池,等待待处理的事件,当处理完事件后进程不会销毁,仍然在进程池中等待处理其他事件,直到事件全部处理完毕,进程退出。 进程的复用降低了资源的消耗。

实现进程池

import time, os

from multiprocessing import Pool


def worker(msg):
    start_time = time.time()
    print(F"{msg}开始执行,进程pid为{os.getpid()}")
    time.sleep(1)
    end_time = time.time()
    print(F"{msg}执行完毕,耗时{end_time - start_time}")


def main():
    po = Pool(3)    # 定义进程池最大进程数为3
    for i in range(10):
        # 每次循环会用空闲出的子进程调用目标
        po.apply_async(worker, args=(i,))   # 若调用的函数报错,进程池中不会打印报错信息

    po.close()  # 关闭进程池,关闭后,不再接收新的目标
    po.join()   # 等待进程池中所有子进程执行完,必须放在close()之后。若没有join()操作,主进程执行完后直接关闭
    print("--end--")


if __name__ == "__main__":
    main()

线程

在早期的操作系统中并没有线程的概念,进程是拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明了线程,线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID,当前指令指针PC,寄存器和堆栈组成。而进程由内存空间(代码,数据,进程空间,打开的文件)和一个或多个线程组成。

实现多线程

import time
import threading


def say(sleep_time):
    for i in range(5):
        print(f"说{i+1}下")
        time.sleep(sleep_time)

def dance():
    for i in range(10):
        print(f"跳{i+1}下")
        time.sleep(1)

def main():
    # 创建线程对象
    # target:指定线程调用的函数名。注:等号后跟方法名不能加括号,如果加了也能执行函数但threading功能无效
    # args:指定调用函数时传递的参数。注:args是一个数组变量参数,只传一个参数时,需要在参数后面添加逗号
    t1 = threading.Thread(target=say, args=(1,))
    t2 = threading.Thread(target=dance)

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

    # 查看正在运行的线程
    while True:
        now_threading = threading.enumerate()
        print(now_threading)
        # 当子线程全部运行结束后,仅剩1个主线程
        if len(now_threading) <= 1:
            break
        time.sleep(1)


if __name__ == "__main__":
    main()

多线程的资源竞争问题

因为多线程共享全局变量,当线程还没执行完当前任务,操作系统就自动轮流调度执行其他任务,就可能会产生资源竞争的问题。

比如下例中,执行 g_num+=1 时,会将其分成3步执行:1.取值;2.运算;3.保存运算结果,在CPU执行任务时,若刚运行1 2 步就交替执行下一个任务,再返回来保存结果,因为共享全局变量,此时运算结果可能已被重新赋值。

import time
import threading

g_num = 0


def sum1(num):
    global g_num
    for i in range(num):
        g_num += 1
    print(F"sum1:{g_num}")


def sum2(num):
    global g_num
    for i in range(num):
        g_num += 1
    print(F"sum2:{g_num}")


def main():
    t1 = threading.Thread(target=sum1, args=(1000000,))
    t2 = threading.Thread(target=sum2, args=(1000000,))
    t1.start()
    t2.start()
    time.sleep(2)
    print(g_num)    # 执行后,预期结果为2000000;实际不是


if __name__ == "__main__":
    main()

执行结果

从结果可以看出,sum1和sum2不为1000000,总和不为2000000,这就是上面说的资源竞争问题

互斥锁解决资源竞争问题

import threading
import time

# 定义一个全局变量
g_num = 0

# 创建一个互斥锁,默认是没有上锁的
mutex = threading.Lock()


def sum1(num):
    global g_num
    # mutex.acquire()     # 若在此处上锁,要等下面循环执行完才会解锁,若循环时间太长,会导致另外的线程堵塞等待。
    for i in range(num):
        # 上锁,如果之前没有被上锁,那么此时上锁成功。 上锁原则:一般对产生资源竞争的代码上锁。如果上锁之前 已经被上锁了,那么此时会堵塞在这里,直到 这个锁被解开为止。
        mutex.acquire()
        g_num += 1
        # 解锁
        mutex.release()
    print("-----in test1 g_num=%d----" % g_num)


def sum2(num):
    global g_num
    for i in range(num):
        mutex.acquire()
        g_num += 1
        mutex.release()
    print("-----in test2 g_num=%d=----" % g_num)


def main():
    t1 = threading.Thread(target=sum1, args=(1000000,))
    t2 = threading.Thread(target=sum2, args=(1000000,))

    t1.start()
    t2.start()

    # 等待上面的2个线程执行完毕....
    time.sleep(2)

    print("-----in main Thread g_num = %d---" % g_num)


if __name__ == "__main__":
    main()

运行结果

死锁

在线程间共享多个资源的时候,如果两个线程分别占用部分资源并且同时等待对方的资源,就会造成死锁。尽管死锁很少发生,但一旦发生就会造成应用停止响应。下面看一个死锁例子。

import time
import threading


# 创建多个锁
mutexA = threading.Lock()
mutexB = threading.Lock()


def print1():
    mutexA.acquire()
    time.sleep(2)   # 等待B锁稳定
    print("打印A1")
    mutexB.acquire()
    print("打印B1")
    mutexB.release()
    mutexA.release()


def print2():
    mutexB.acquire()
    time.sleep(1)   # 等待A锁稳定
    print("打印B2")
    mutexA.acquire()
    print("打印A2")
    mutexA.release()
    mutexB.release()


def main():
    t1 = threading.Thread(target=print1)
    t2 = threading.Thread(target=print2)

    t1.start()
    t2.start()


if __name__ == "__main__":
    main()

执行结果

 避免死索办法:1、添加超时时间;2、银行家算法(让锁按预期上锁和解锁)

协程

协程,又称微线程。协程的作用是在执行函数A时可以随时中断去执行函数B,然后中断函数B继续执行函数A(可以自由切换)。但这一过程并不是函数调用,这一整个过程看似像多线程,然而协程只有一个线程执行

协程的优势:

  • 执行效率极高,因为子程序切换(函数)不是线程切换,由程序自身控制,没有切换线程的开销。所以与多线程相比,线程的数量越多,协程性能的优势越明显。
  • 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在控制共享资源时也不需要加锁,因此执行效率高很多。

gevent

gevent是第三方库,通过 greenlet 实现 coroutine,创建、调度的开销比 线程(thread) 还小,因此程序内部的 执行流 效率高。

其基本思想是:当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。

gevent常用方法:

  • gevent.spawn() 创建一个普通的Greenlet对象并切换
  • gevent.spawn_later(seconds=3) 延时创建一个普通的Greenlet对象并切换
  • gevent.spawn_raw() 创建的协程对象属于一个组
  • gevent.getcurrent() 返回当前正在执行的greenlet
  • gevent.joinall(jobs) 将协程任务添加到事件循环,接收一个任务列表
  • gevent.wait() 可以替代join函数等待循环结束,也可以传入协程对象列表
  • gevent.kill() 杀死一个协程
  • gevent.killall() 杀死一个协程列表里的所有协程
  • monkey.patch_all() 非常重要,会自动将python的一些标准模块替换成gevent框架
import gevent


def task(n):
    for i in range(n):
        print(gevent.getcurrent(), i)


if __name__ == '__main__':
    g1 = gevent.spawn(task, 3)
    g2 = gevent.spawn(task, 3)
    g3 = gevent.spawn(task, 3)

    g1.join()
    g2.join()
    g3.join()

运行结果

可以看到3个greenlet是依次运行而不是交替运行。要让greenlet交替运行,可以通过gevent.sleep()交出控制权:

import gevent


def task(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        gevent.sleep(1)


if __name__ == '__main__':
    g1 = gevent.spawn(task, 3)
    g2 = gevent.spawn(task, 3)
    g3 = gevent.spawn(task, 3)

    g1.join()
    g2.join()
    g3.join()

运行结果

当然在实际的代码里,我们不会用gevent.sleep()去切换协程,而是在执行到IO操作时gevent会自动完成,所以gevent需要将Python自带的一些标准库的运行方式由阻塞式调用变为协作式运行。这一过程在启动时通过monkey patch完成:

import time
import gevent
from gevent import monkey
# 猴子补丁,会自动将python的一些标准模块替换成gevent框架。慎用,它创造了“隐式的副作用”,如果出现问题 它很多时候是极难调试的。
monkey.patch_all()  # 注意:若导出的模块函数不会被替换,比如from time import sleep,sleep不会被替换


def task(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(1)   # 会被gevent自动替换为gevent.sleep()


if __name__ == '__main__':
    g1 = gevent.spawn(task, 3)
    g2 = gevent.spawn(task, 3)
    g3 = gevent.spawn(task, 3)

    g1.join()
    g2.join()
    g3.join()

执行结果

上面的流程看起来比较繁琐,可以使用 gevent.joinall() 方法简化流程:

import time
import gevent
from gevent import monkey
# 猴子补丁,会自动将python的一些标准模块替换成gevent框架。慎用,它创造了“隐式的副作用”,如果出现问题 它很多时候是极难调试的。
monkey.patch_all()  # 注意:若导出的模块函数不会被替换,比如from time import sleep,sleep不会被替换


def task(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(1)   # 会被gevent自动替换为gevent.sleep()


if __name__ == '__main__':
    gevent.joinall([
        gevent.spawn(task, 4),
        gevent.spawn(task, 4),
        gevent.spawn(task, 4),
    ])

执行结果


-事必有法,然后有成- 最后祝大家早日达到测试的天花板!



 以下是我收集到的比较好的学习教程资源,虽然不是什么很值钱的东西,如果你刚好需要,可以评论区,留言【777】直接拿走就好了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/719684.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

“多”维演进:智能编码的深度进化

我们在追求怎样的编码未来&#xff1f; 无处不在的视频渗透、井喷式的流量增长、多元的场景技术需求、用户对视频体验的“不将就”……音视频行业的快速发展却伴随着“编码标准升级速度缓慢”、“硬件红利见底”、“编码复杂度带来的成本问题”等众多挑战。 视频编码还“卷”得…

SQL Server 2008 r2 修改sa密码 通过sql server 身份验证登陆

SQL Server的两种身份验证模式是Windows验证模式和SQL Server验证模式。 sql server 2008 r2 修改sa密码 通过sql server 身份验证登陆 【解决方法如下】 通过windows 身份验证登陆后,首先选中服务器(右键)->属性 ->安全性->服务器身份验证修改为"SQL SERVER和W…

浅谈电瓶车在线充电管理系统的设计与应用

安科瑞 华楠 摘要&#xff1a;基于ARM 硬件平台&#xff0c;研究了电瓶车充电技术&#xff0c;提出了一种智能型电瓶车在线安全充电系统&#xff0c;该系统可根据实际充电情况实现智能断电&#xff0c;同时提供给用户一种远程充电、断电的平台&#xff0c;目的是防止电瓶车过度…

港联证券|个人的分红要交税吗?

近年来&#xff0c;随着经济的快速发展&#xff0c;越来越多的人开始关注个人财务管理&#xff0c;其中一个重要的问题就是个人的分红是否需要缴纳税款。这个问题并不简单&#xff0c;需要从多个角度进行综合分析。 首先&#xff0c;我们需要明确一点&#xff0c;个人的分红属于…

浅谈数据中心机房动环监控系统可视化设计与研究

安科瑞电气股份有限公司 上海嘉定 201801 摘要&#xff1a;为了维护好数据中心机房动力环境&#xff0c;及时发现隐患和排除故障&#xff0c;降低管理成本控制能耗&#xff0c;提高运维效率&#xff0c;保障数据中心安全、高效、环保、稳定的运行&#xff0c;针对目前机房管理…

通过五点判断CRM系统是否好用

CRM管理系统在当今市场竞争中扮演着越来越重要的角色。因此&#xff0c;企业选择一款适合自己的CRM系统是非常关键的。那么&#xff0c;如何评价一款CRM系统是否好用&#xff1f; 1、功能是否全面 好用的CRM系统应该能够覆盖企业与客户交互的全过程&#xff0c;包括营销管理、…

【分布式技术专题】「缓存解决方案」一文带领你好好认识一下企业级别的缓存技术解决方案的运作原理和开发实战(数据缓存不一致分析)

一文带领你好好认识一下企业级别的缓存技术解决方案的运作原理和开发实战&#xff08;数据缓存不一致问题分析&#xff09; 数据不一致的原因逻辑失败导致的数据不一致物理失败导致的数据不一致 数据一致性的解决方案消费消息异步删除缓存主要流程如下图所示 订阅Binlog利用队列…

只出现一次的数字

题目链接 只出现一次的数字 题目描述 注意点 1 < nums.length < 30000-30000 < nums[i] < 30000除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次 解答思路 最初想到使用一种数据结构将元素存储起来&#xff0c;但是空间复杂度为O(n)&#xff0…

Linux(ubuntu)上安装vmware workstation虚拟机

Linux&#xff08;ubuntu&#xff09;上安装vmware workstation虚拟机 首先下载vmware workstation 官网下载地址&#xff1a;https://www.vmware.com/products/workstation-pro/workstation-pro-evaluation.html 下滑至页面下方下载&#xff1a; 或者点击此处下载 下载完成后…

首发价11999元?华为智慧屏S3Pro电视7月10日上市

华为最新推出了两款全新的智慧屏 S3 Pro&#xff0c;分别是65英寸和75英寸版本&#xff0c;售价分别为5999元和7999元。除此之外&#xff0c;华为还推出了全新的S3 Pro 86英寸型号&#xff0c;首发价为11999元。这款电视将于7月10日上市&#xff0c;对于感兴趣的用户来说&#…

押注数字人,百度、科大讯飞“短兵相接”

配图来自Canva可画 近两年&#xff0c;小雀斑、柳夜熙、AYAYI等大量网红数字人的相继出现&#xff0c;以及虚拟数字技术在《指环王》和《阿凡达》以及《刺杀小说家》等电影中的广泛应用&#xff0c;还有北京冬奥会期间数字人在手语解说、节目直播等众多场合亮相&#xff0c;使…

2024浙大GMSCM提面第一场:全英文项目的生死选择题

本周末是今年浙大MBA非全英文项目GMSCM的第一场提前批面试&#xff0c;作为诸多方向中相对比较稳定的项目之一&#xff0c;GMSCM项目每年的提前批面试申请也都表现的比较稳健&#xff0c;而其目前主要的招生也是依托于提前批面试&#xff0c;根据这几年的情况&#xff0c;每年浙…

vue+cesium 获取鼠标浮动的经纬度

一、需求 是在地图中展示鼠标的经纬度 二、成果展示 三、思路和方法 html <div class"mouseMove" v-if"mouseMove.lat"><div v-if"mouseMove.lat">经度&#xff1a;{{ mouseMove.lng }}</div><div v-if"mouseMove…

关于研究SELECT * 是否会导致SQL查询效率变低的问题

引言 无论在工作还是面试中&#xff0c;关于SQL中不要用“SELECT *”的问题&#xff0c;经常会被大家用作讨论&#xff0c;虽说听烂了&#xff0c;但普遍理解还是在很浅的层面&#xff0c;并没有多少人去追根究底&#xff0c;探究其原理。 一、效率低的原因 先看一下最新《阿…

计算机网络_ 1.3 网络核心(数据交换_电路交换_多路复用)

计算机网络_数据交换_电路交换_多路复用 多路复用频分多路复用FDM时分多路复用TDM波分多路复用WDM码分多路复用CDM 多路复用 多路复用&#xff08;Multiplexing&#xff09;&#xff0c;简称复用&#xff0c;是通信技术的基本概念。 链路/网络资源&#xff08;如带宽&#x…

【动态规划算法】第四题:91.解码方法

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树 &#x1f389;作者宣言&#xff1a;认真写好每一篇博客 &#x1f38a;作者gitee:gitee &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法 如 果 你 喜 欢 作 者 的 文 章 &#xff0c;就 给 作…

一建建筑周超口袋书

第一部分 建筑工程技术1A414000 建筑工程材料[B-4,2021] 常见的高分子防水卷材有哪些?三元乙丙、聚氯乙烯、氯化聚乙烯、氯化聚乙烯-橡胶共混及三元丁橡胶防水卷材记忆技巧三单数年考试中&#xff0c;2011 年屋面女儿墙渗漏水处理&#xff0c;2015 年女儿墙防水识图找错&#…

element ui table 状态用switch展示

效果图如下 方法一&#xff1a;将图片封装成组件 1.&#xff1a;ImgswitchOpen.vue 2&#xff1a;页面引入组件 3&#xff1a;使用 方法二&#xff1a;引入图片 1&#xff1a;引入图片 2&#xff1a;在data中定义 3.使用

垂直领域大模型:从医疗ChatDoctor到金融BloombergGPT、法律ChatLaw/LawGPT_zh

第一部分 各种医疗类ChatGPT&#xff1a;或中英文数据微调LLaMA、或中文数据微调ChatGLM 1.1 基于LLaMA微调的中英文版ChatDoctor 1.1.1 ChatDoctor&#xff1a;通过self-instruct技术提示API的数据和医患对话数据集微调LLaMA Github上有一个基于LLaMA模型的医疗微调模型&am…

c++高性能264/265实时h5流媒体服务器前后端整体解决方案

c高性能264/265实时h5流媒体服务器前后端整体解决方案 1.效果展示 下图展示了前端播放效果。 播放1路264视频流&#xff0c;4路265视频流 CPU占用率10%(测试机器上运行着c服务端和其他工具程序) GPU0占用率17% 1.1 作者测试机器配置 处理器 11th Gen Intel Core™ i7-118…