品牌 资讯 搭配 材料 时尚 热点 行业 首饰 玉石 行情

Python多任务教程:进程、线程、协程

2023-07-08 15:18:19 来源:博客园

1.进程

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

进程具有的特征:


【资料图】

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

实现多进程

import multiprocessingimport timedef 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个进程)

2.进程池

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

实现进程池

import time, osfrom multiprocessing import Pooldef 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()

3.线程

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

实现多线程

import timeimport threadingdef 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 timeimport threadingg_num = 0def 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 threadingimport 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 timeimport 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、银行家算法(让锁按预期上锁和解锁)

4.协程

协程,又称微线程。协程的作用是在执行函数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 geventdef 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 geventdef 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 timeimport geventfrom 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 timeimport geventfrom gevent import monkey# 猴子补丁,会自动将python的一些标准模块替换成gevent框架。慎用,它创造了“隐式的副作用”,如果出现问题 它很多时候是极难调试的。monkey.patch_all()  # 注意:若导出的模块函数不会被替换,比如from time import sleep,sleep不会被替换"""学习中遇到问题没人解答?小编创建了一个Python学习交流群:711312441寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!"""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),    ])

执行结果

标签:

(责任编辑:new01)

相关文章

Python多任务教程:进程、线程、协程

​ 1 进程进程是一个具有一定独立功能的程序在一个数据集上的一次动态

2023-07-08 15:18:19

索罗斯的话

​1、如果你没有做好承受痛苦的准备,那就离开吧。别指望会成为常胜将军

2023-07-08 14:17:37

中国队出征女足世界杯

​7月7日,中国女足从广州白云国际机场出发,踏上2023女足世界杯的征程。

2023-07-08 13:02:24

全国见义勇为勇士榜发布 天津2人上榜

​天津北方网讯:昨日从市委政法委获悉,中央政法委日前发布了2023年第二

2023-07-08 11:50:51

证监会首席律师焦津洪:加快推进资本市场投资端的改革十分紧迫

​证券时报网讯,7月8日,在第九届“青岛&bull;中国财富论坛”上,中国证

2023-07-08 11:35:36

“他们好辛苦,娃娃很能干!”强降雨致山体滑坡 救援队翻山蹚水转移村民

​受强降雨影响,重庆万州长滩镇土门村发生山体滑坡,导致道路受损、

2023-07-08 08:46:10

前“福建首富”57亿私有化达利食品,这些年亏了还是赚了?

​此外,张毅认为,达利食品和同行相比,整体经营还是有可圈可点之处,但

2023-07-08 07:58:49

三星电视依赖中国面板:超过一半来自中国四家公司

​据报道,三星电子生产的电视机中,超过一半的面板来自其他厂商。详细数

2023-07-08 06:42:53

记者报尤文高层职责安排:吉恩托利任足球总监,斯卡纳维诺任CEO

​直播吧7月7日讯BianconeraNews记者MirkoNicolino报道了尤文图斯高层人

2023-07-08 04:39:15

多项经济指标回升 我国经济向好发展态势明显

​央视网消息(新闻联播):7月5日,公布的多项经济指标显示,从电商物流

2023-07-07 23:07:47

邓一杰:非农继续稳定收获!

​大大小小的非农,也经历不下几十次了,唯一的区别只是收获多少的问题!

2023-07-07 22:01:21

兵天绿诚顺利通过CNAS扩项认可评审

​7月7日,记者从新疆天业集团获悉,近日,从中国合格评定国家认可委员会

2023-07-07 21:15:27

中注协惩戒违规会计师事务所、注册会计师

​中国注册会计师协会7日公布的消息披露,一批违规会计师事务所、注册

2023-07-07 20:30:33

全球数字经济创新大赛,这家北京企业获得总冠军!

​在7月7日举行的2023全球数字经济创新大赛总决赛上,北京企业的“机场无

2023-07-07 19:32:16

内蒙古准格尔旗:暖心驿站为户外劳动者撑起一片“绿荫”

​烈日当头的盛夏,环卫工人张成泉将扫帚、簸箕收拾妥当,走进位于迎泽街

2023-07-07 18:59:25

AI重塑千行百业 华为云发布盘古大模型3.0和昇腾AI云服务

​华为开发者大会2023(Cloud)7月7日在中国东莞正式揭开帷幕,并同时在

2023-07-07 18:25:13

天津市滨海新区获批建设国家级创新驱动示范区

​天津滨海新区获批建设全国创新驱动示范区日前,中国科协发布《关于支持

2023-07-07 17:41:19

中电港:上半年净利预降54.14%-59.54%

​金融界7月7日消息中电港公告,预计2023年半年度归母净利7500万元-8500

2023-07-07 17:20:28

无权转租的房屋租赁合同有效吗?

​无权转租的合同效力是效力待定的。无权处分行为属效力待定行为。承租人

2023-07-07 16:55:58

【短期融资券新发公告】23成都高新SCP002今日发布发行公告

​附件:成都高新投资集团有限公司2023年度第二期超短期融资券募集说明书

2023-07-07 16:07:05

美防长声称北京在南海行为是“胁迫性的”和“危险的”,中方驳斥

​【环球时报-报道记者乌元春】外交部发言人汪文斌主持7月7日的例行记者

2023-07-07 16:00:02

7月7日国内苯酐产业链部分价格上涨

​产品7月6日7月7日涨跌幅单位:元 吨OX810081000元 吨苯酐762576250元

2023-07-07 15:20:52

香港青年组团赴广西参访 感受壮乡文化

​【港澳台专线】香港青年组团赴广西参访 感受壮乡文化 (张广权)“

2023-07-07 14:46:44

让利型基金要来了?与浮动费率产品差异较大 业内多家机构称已上报实施方案

​【让利型基金要来了?与浮动费率产品差异较大业内多家机构称已上报实施

2023-07-07 14:01:40

再鼎医药首席财务官曹基哲离职 陈娅静接任

​中国网财经7月7日讯(记者张增艳)再鼎医药日前公告称,公司于7月6日(美

2023-07-07 13:29:35

贵州习酒上半年实现销售116亿:被指向经销商压货,张德芹曾称希望大家赚到钱

​近日,贵州习酒高调发布经营情况:2023年上半年实现销售额116亿元,同

2023-07-07 13:06:33

傲农生物2.5亿新设生物科技集团子公司

​证券时报e公司讯,企查查APP显示,近日,福建傲芯生物科技集团有限公司

2023-07-07 11:52:24

冒充“能人”诈骗巨款 镇赉公安出击破案

​前不久,镇赉县公安局成功侦破系列谎称“能人”实施诈骗案件,涉案金额

2023-07-07 11:18:42

央行今日开展20亿元逆回购操作

​新京报贝壳财经讯为维护银行体系流动性合理充裕,2023年7月7日人民银行

2023-07-07 10:40:37

京东方于成都投资新设科技公司 注册资本1亿元

​企查查APP显示,近日,京东方艺云(成都)科技有限公司成立,法定代表

2023-07-07 10:05:43