精华内容
下载资源
问答
  • 线程、进程、多线程、多进程 和 多任务 小结

    千次阅读 多人点赞 2019-04-20 11:59:56
    3 多进程 4 多线程 5 线程与进程的关系 6 线程和进程的区别 7 进程的优缺点 7.1 进程的优点 7.2 进程的缺点 8 线程的优缺点 8.1 线程的优点 8.2 线程的缺点 9 多线程的优缺点 9.1 多线程的优点 9.2 多...

    目录

    1 进程

    2 线程

    3 多进程

    4 多线程

    5 线程与进程的关系

    6 线程和进程的区别

    7 进程的优缺点

    7.1 进程的优点

    7.2 进程的缺点

    8 线程的优缺点

    8.1 线程的优点

    8.2 线程的缺点

    9 多线程的优缺点

    9.1 多线程的优点

    9.2 多线程的缺点

    10多进程的优缺点

    10.1 多进程的优点

    10.2 多进程的缺点

    8 多任务(多进程)

    参考资料


    最近经常看到 多进程,多线程和多任务等名词,很容易混。网上查了很多资料,内容很多。作为Linux初学者,还是想从最基础的开始了解,找通俗的例子了解,由浅入深。我把网上查阅的资料整理的一下,一次性全部摸透,还是有点难度的。写个博客,记录一下,以便后期查阅复习。

    首先,从定义开始,先看一下教科书上 进程线程定义:

    进程:资源分配的最小单位。

    线程:程序执行的最小单位。

    心中默念,啥啥啥,写的这是啥。于是乎 我就想到王宝强一脸懵逼的表情。

     

    1 进程

    进程是程序执行时的一个实例,即它是程序已经执行到课中程度的数据结构的汇集。从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的基本单位。

    举例说明进程

    想象一位有一手好厨艺的计算机科学家正在为他的女儿烘制生日蛋糕。他有做生日蛋糕的食谱,厨房里有所需的原料:面粉、鸡蛋、糖、香草汁等。在这个比喻中,做蛋糕的食谱就是程序(即用适当形式描述的算法)计算机科学家就是处理器(CPU),而做蛋糕的各种原料就是输入数据。进程就是厨师阅读食谱、取来各种原料以及烘制蛋糕等一系列动作的总和。现在假设计算机科学家的儿子哭着跑了进来,说他的头被一只蜜蜂蛰了。计算机科学家就记录下他照着食谱做到哪儿了(保存进程的当前状态),然后拿出一本急救手册,按照其中的指示处理蛰伤。这里,我们看到处理机制是从一个进程(做蛋糕)切换到另一个高优先级的进程(实施医疗救治),每个进程拥有各自的程序(食谱和急救手册)。当蜜蜂蛰伤处理完之后,这位计算机科学家又回来做蛋糕,从他离开时的那一步继续做下去。

     

    2 线程

    线程是CPU调度的最小单位(程序执行流的最小单元),它被包含在进程之中,是进程中的实际运作单元。一条线程是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

    一个标准的线程有线程ID、当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单元,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现处间断性。

    线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。

     

    举例说明线程

    假设,一个文本程序,需要接受键盘输入,将内容显示在屏幕上,还需要保存信息到硬盘中。若只有一个进程,势必造成同一时间只能干一样事的尴尬(当保存时,就不能通过键盘输入内容)。若有多个进程,每个进程负责一个任务,进程A负责接收键盘输入的任务,进程B负责将内容显示在屏幕上的任务,进程C负责保存内容到硬盘中的任务。这里进程A,B,C间的协作涉及到了进程通信问题,而且有共同都需要拥有的东西——-文本内容,不停的切换造成性能上的损失。若有一种机制,可以使任务A,B,C共享资源,这样上下文切换所需要保存和恢复的内容就少了,同时又可以减少通信所带来的性能损耗,那就好了。这种机制就是线程

    总的来说进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间)。

     

     

    3 多进程

    进程是程序在计算机上的一次执行活动。当你运行一个程序,你就启动了一个进程。显然,程序是死的(静态的),进程是活的(动态的)。进程可以分为系统进程用户进程。凡是用于完成操作系统的各种功能的进程就是系统进程,它们就是处于运行状态下的操作系统本身;所有由用户启动的进程都是用户进程。进程是操作系统进行资源分配的单位。 进程又被细化为线程,也就是一个进程下有多个能独立运行的更小的单位。在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态,这便是多任务。现代的操作系统几乎都是多任务操作系统,能够同时管理多个进程的运行。 多任务带来的好处是明显的,比如你可以边听网易云音乐,一边上网,与此同时甚至可以将下载的文档打印出来,而这些任务之间丝毫不会相互干扰。那么这里就涉及到并行的问题,俗话说,一心不能二用,这对计算机也一样,原则上一个CPU只能分配给一个进程,以便运行这个进程。我们通常使用的计算机中只有一个CPU,也就是说只有一颗心,要让它一心多用,同时运行多个进程,就必须使用并发技术。实现并发技术相当复杂,最容易理解的是“时间片轮转进程调度算法”,它的思想简单介绍如下:在操作系统的管理下,所有正在运行的进程轮流使用CPU,每个进程允许占用CPU的时间非常短(比如10毫秒),这样用户根本感觉不出来 CPU是在轮流为多个进程服务,就好像所有的进程都在不间断地运行一样。但实际上在任何一个时间内有且仅有一个进程占有CPU。 如果一台计算机有多个CPU,情况就不同了,如果进程数小于CPU数,则不同的进程可以分配给不同的CPU来运行,这样,多个进程就是真正同时运行的,这便是并行

    并行处理(Parallel Processing)是计算机系统中能同时执行两个或更多个处理的一种计算方法。并行处理可同时工作于同一程序的不同方面。并行处理的主要目的是节省大型和复杂问题的解决时间。并发处理(concurrency Processing):指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机(CPU)上运行,但任一个时刻点上只有一个程序在处理机(CPU)上运行

    并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。所以说,并行是并发的子集。

     

     

    4 多线程

    线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单元。在单一程序中同时运行多个想成完成不同的工作,称为多线程

    多线程是为了使得多个线程并行的工作以完成多项任务,以提高系统的效率。线程是在同一时间需要完成多项任务的时候被实现的。

    打个比方:

    多进程是立体交通系统(近似于立交桥),虽然造价高,上坡下坡多耗点油,但是不堵车。

    多线程是平面交通系统,造价低,但红绿灯太多,老堵车。

     

     

    5 线程与进程的关系

    (1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程;

    (2)资源分配给进程,同一进程内的所有线程共享该进程的所有资源;

    (3)线程在执行过程中需要协作同步。不同进程中的线程之间要利用消息通信的方法实现同步;

    (4)处理机分配给线程,即真正在处理机上运行的是线程;

    (5)线程是进程的一个执行单元,也是进程内的可调用实体。

     

     

    6 线程和进程的区别

    (1)线程共享内存空间;进程的内存是独立的;

    (2)同一个进程的线程之间可以直接交流;两个进程想通信,必须通过一个中间代理来实现;

    (3)创建新进程很简单;创建新进程需要对其父进程进行一个克隆;

    (4)一个线程可以控制和操作同一进程里的其他线程;但是进程只能操作子进程;

    (5)改变注线程(如优先权),可能会影响其他线程;改变父进程,不影响子进程。

    (6)调度:线程作为分配和调度的基本单位,进程作为拥有资源的基本单位

    (7)并发性:不进进程之间可以并发执行,同一进程内的线程也可以并发执行

    (8)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但是可以访问隶属于进程的系统资源

    (9)系统开销:在创建和撤销进程的时候,系统都要分配和回收资源,导致系统的明显大于创建和撤销线程时的开销。但进程有独立的地址空间,进程崩溃后,在保护模式的下不会对其他进程造成影响,而线程只是进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有独立的地址空间,一个线程死后就等于整个进程死掉,所以多进程程序要比多线程程序健壮,但是在进程切换的时候消耗的资源较大,效率差。

    根本区别就一点:用多进程每个进程有自己的地址空间(address space),线程则共享地址空间。

    总结:多线程执行效率高;  多进程耗资源,安全。

     

     

    7 进程的优缺点

    7.1 进程的优点

    1)顺序程序的特点:具有封闭性和可再现性;

    2)程序的并发执行和资源共享。多道程序设计出现后,实现了程序的并发执行和资源共享,提高了系统的效率和系统的资源利用率。

     

    7.2 进程的缺点

    操作系统调度切换多个线程要比切换调度进程在速度上快的多。而且进程间内存无法共享,通讯也比较麻烦。线程之间由于共享进程内存空间,所以交换数据非常方便;在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。  

     

     

    8 线程的优缺点

    8.1 线程的优点

    1)它是一种非常"节俭"的多任务操作方式。在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码 段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程 所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。当然,在具体的系统上,这个数据可能 会有较大的区别;

    2)线程间方便的通信机制,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便;

    3)使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上;

    4)改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

     

    8.2 线程的缺点

    1)调度时, 要保存线程状态,频繁调度,需要占用大量的机时;

    2)程序设计上容易出错(线程同步问题)。

     

     

    9 多线程的优缺点

    9.1 多线程的优点

    1)无需跨进程边界; 程序逻辑和控制方式简单;

    2)所有线程可以直接共享内存和变量等;

    3)线程方式消耗的总资源比进程方式好。

     

    9.2 多线程的缺点

    1)每个线程与主程序共用地址空间,受限于2GB地址空间;

    2)线程之间的同步和加锁控制比较麻烦; 一个线程的崩溃可能影响到整个程序的稳定性;

    3)到达一定的线程数程度后,即使再增加CPU也无法提高性能,例如Windows Server 2003,大约是1500个左右的线程数就快到极限了(线程堆栈设定为1M),如果设定线程堆栈为2M,还达不到1500个线程总数;

    4)线程能够提高的总性能有限,而且线程多了之后,线程本身的调度也是一个麻烦事儿,需要消耗较多的CPU 。

     

    10多进程的优缺点

    10.1 多进程的优点

    1)每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系;

    2)通过增加CPU,就可以容易扩充性能;

    3)可以尽量减少线程加锁/解锁的影响,极大提高性能,就算是线程运行的模块算法效率低也没关系;

    4)每个子进程都有2GB地址空间和相关资源,总体能够达到的性能上限非常大。

     

    10.2 多进程的缺点

    1)逻辑控制复杂,需要和主程序交互;

    2)需要跨进程边界,如果有大数据量传送,就不太好,适合小数据量传送、密集运算 多进程调度开销比较大。

     

    总结:最好是多进程和多线程结合,即根据实际的需要,每个CPU开启一个子进程,这个子进程开启多线程可以为若干同类型的数据进行处理。当然你也可以利用多线程+CPU+轮询方式来解决问题……方法和手段是多样的,关键是自己看起来实现方便有能够满足要求,代价也合适。

     

    按照多个不同的维度(类别),来看看多线程和多进程的对比(注:因为是感性的比较,因此都是相对的,不是说一个好得不得了,另外一个差的无法忍受)。

        对比类别

                                        多进程

                                     多线程

         总结

    数据共享、同步

    数据共享复杂,需要用IPC;数据是分开的,同步简单

    因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂

     各有优势

        内存、CPU

    占用内存多,切换复杂,CPU利用率低

    占用内存少,切换简单,CPU利用率高

     线程占优

     创建销毁、切换

    创建销毁、切换复杂,速度慢

    创建销毁、切换简单,速度很快

     线程占优

        编程、调试

    编程简单,调试简单

    编程复杂,调试复杂

     进程占优

           可靠性

    进程间不会互相影响

    一个线程挂掉将导致整个进程挂掉

     进程占优

           分布式

    适应于多核、多机分布式;如果一台机器不够,扩展到多台机器比较简单

    适应于多核分布式

     进程占优

    其实没有绝对的好与坏,只有哪个更加合适的问题。我们来看实际应用中究竟如何判断更加合适。

    1)需要频繁创建销毁的优先用线程

    这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的

     

    2)需要进行大量计算的优先使用线程

    所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。这种原则最常见的是图像处理、算法处理。

     

    3)强相关的处理用线程,弱相关的处理用进程

    什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。

    一般的Server需要完成如下任务:消息收发、消息处理。“消息收发”和“消息处理”就是弱相关的任务,而“消息处理”里面可能又分为“消息解码”、“业务处理”,这两个任务相对来说相关性就要强多了。因此“消息收发”和“消息处理”可以分进程设计,“消息解码”、“业务处理”可以分线程设计。当然这种划分方式不是一成不变的,也可以根据实际情况进行调整。

     

    4)可能要扩展到多机分布的用进程,多核分布的用线程

     

    5)都满足需求的情况下,用你最熟悉、最拿手的方式

    至于“数据共享、同步”、“编程、调试”、“可靠性”这几个维度的所谓的“复杂、简单”应该怎么取舍,我只能说:没有明确的选择方法。但我可以告诉你一个选择原则:如果多进程和多线程都能够满足要求,那么选择你最熟悉、最拿手的那个。需要提醒的是:虽然我给了这么多的选择原则,但实际应用中基本上都是“进程+线程”的结合方式,千万不要真的陷入一种非此即彼的误区。

     

    8 多任务(多进程)

    现代操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持“多任务”的操作系统。

    什么叫“多任务”呢?简单地说,就是操作系统可以同时运行多个任务。打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word写论文,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。

    现在,多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢

    其实操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

    真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。

    对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。

    有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些 “子任务” 称为线程(Thread)。

    由于每个进程至少要干一件事,所以,一个进程至少有一个线程。当然,像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。当然,真正地同时执行多线程需要多核CPU才可能实现。

     

     

    参考资料

    [1] https://www.cnblogs.com/pingqiang/p/8007549.html#多进程多线程

    [2] https://www.cnblogs.com/ww36315610/p/3881533.html?tdsourcetag=s_pctim_aiomsg

    [3] http://www.cnblogs.com/Yogurshine/p/3640206.html

    [4] https://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/0013868322563729e03f6905ea94f0195528e3647887415000?tdsourcetag=s_pctim_aiomsg

    [5] https://www.cnblogs.com/yuanchenqi/articles/6755717.html#_label3?tdsourcetag=s_pctim_aiomsg

    [6] https://www.cnblogs.com/zhanht/p/5401685.html?tdsourcetag=s_pctim_aiomsg

    [7] https://www.kafan.cn/edu/85866456.html

     

    展开全文
  • python 彻底解读多线程与多进程

    万次阅读 多人点赞 2019-03-26 14:20:34
    title: 多线程与多进程 copyright: true top: 0 date: 2019-03-03 16:16:41 tags: 多线程多进程 categories: Python高阶笔记 permalink: password: keywords: description: 对python的多线程多进程进一步刨析。 真是...

    title: 多线程与多进程
    copyright: true
    top: 0
    date: 2019-03-03 16:16:41
    tags: 多线程多进程
    categories: Python高阶笔记
    permalink:
    password:
    keywords:
    description: 对python的多线程多进程进一步刨析。

    真是这样的话,有些话,只有准确的时间准确的地点亲口说出来。现在时间错过了,再说也没用了

    在此之前请完整阅读完

    Python threading 多线程模块

    Python multiprocess 多进程模块

    GIL 全局解释器锁

    GIL(全局解释器锁,GIL 只有cpython有):在同一个时刻,只能有一个线程在一个cpu上执行字节码,没法像c和Java一样将多个线程映射到多个CPU上执行,但是GIL会根据执行的字节码行数(为了让各个线程能够平均利用CPU时间,python会计算当前已执行的微代码数量,达到一定阈值后就强制释放GIL)和时间片以及遇到IO操作的时候主动释放锁,让其他字节码执行。

    作用:限制多线程同时执行,保证同一个时刻只有一个线程执行。

    原因:线程并非独立,在一个进程中多个线程共享变量的,多个线程执行会导致数据被污染造成数据混乱,这就是线程的不安全性,为此引入了互斥锁。

    互斥锁:即确保某段关键代码的数据只能又一个线程从头到尾完整执行,保证了这段代码数据的安全性,但是这样就会导致死锁。

    死锁:多个子线程在等待对方解除占用状态,但是都不先解锁,互相等待,这就是死锁。

    基于GIL的存在,在遇到大量的IO操作(文件读写,网络等待)代码时,使用多线程效率更高。

    多线程

    一个CPU再同一个时刻只能执行一个线程,但是当遇到IO操作或者运行一定的代码量的时候就会释放全局解释器锁,执行另外一个线程。

    就好像你要烧水和拖地,这是两个任务,如果是单线程来处理这两个任务的话,先烧水,等水烧开,再拖地。这样等待水烧开的时间就白白浪费了,倘若事交给多线程来做的话,就先烧水,烧水的过程中(相当于IO操作的时候)把时间资源让出来给拖地,拖完地后水也烧好了,这个就是多线程的优势,再同一个时间段做更多的事情,也就是再以后会降到的高并发。

    它提供如下一些方法:

    t1 = threading.Thread(target=你写的函数名,args=(传入变量(如果只有一个变量就必须在后加上逗号),),name=随便取一个线程名):把一个线程实例化给t1,这个线程负责执行target=你写的函数名
    t1.start():负责执行启动这个线程
    t1.join():必须要等待你的子线程执行完成后再执行主线程
    t1.setDeamon(True):当你的主线程执行完毕后,不管子线程有没有执行完成都退出主程序,注意不能和t1.join()一起使用。
    threading.current_thread().name:打印出线程名
    

    这些方法一开始看可能会觉得有些多,不过不打紧,可以先把后面的代码看完在回过头看这些提供的方法就觉得很简单了。

    单线程版本

    import time
    
    def mop_floor():
        print('我要拖地了')
        time.sleep(1)
        print('地拖完了')
    
    def heat_up_watrt():
        print('我要烧水了')
        time.sleep(6)
        print('水烧开了')
    
    start_time = time.time()
    heat_up_watrt()
    mop_floor()
    end_time = time.time()
    print('总共耗时:{}'.format(end_time-start_time))
    

    返回结果:

    我要烧水了
    水烧开了
    我要拖地了
    地拖完了
    总共耗时:7.000766277313232
    

    单线程一共耗时7秒

    多线程版本

    import threading
    import time
    
    def mop_floor():
        print('我要拖地了')
        time.sleep(1)
        print('地拖完了')
    
    def heat_up_watrt():
        print('我要烧水了')
        time.sleep(6)
        print('水烧开了')
    
    start_time = time.time()
    t1 = threading.Thread(target=heat_up_watrt)
    t2 = threading.Thread(target=mop_floor)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    end_time = time.time()
    print('总共耗时:{}'.format(end_time-start_time))
    

    返回结果:

    我要烧水了
    我要拖地了
    地拖完了
    水烧开了
    总共耗时:6.000690460205078
    

    可以看到烧水等待的时候直接执行拖地任务,并且总共耗时为6秒,关于这里的start和jion都是固定的操作套路,记住这两个代词以后直接套用即可,需要注意的是多线程程序的执行顺序是不确定的。当执行到sleep语句时,线程将被阻塞(Blocked),到sleep结束后,线程进入就绪(Runnable)状态,等待调度的命令执行另一个子线程,线程调度将自行选择一个线程执行。

    类方法实现多线程

    当遇到比较复杂的业务逻辑或者想要再子线程上面加一些方法的话,可以使用自己的类重写代码,还是使用刚刚的例子:

    import threading
    import time
    
    class mop_floor(threading.Thread):
        def __init__(self):
            super().__init__()
    
        def run(self):
            print('我要拖地了')
            time.sleep(1)
            print('地拖完了')
    
    class heat_up_watrt(threading.Thread):
        def __init__(self,name):
            # 这里传入参数name,就是传入子线程名字
            super().__init__(name=name)
            # 记住这里的格式不能错
    
        def run(self):
            print('我要烧水了')
            print(self.name)
            print(threading.current_thread().name)
            # 这两个都是打印出当前子线程的名字
            time.sleep(6)
            print('水烧开了')
    
    start_time = time.time()
    t1 = mop_floor()
    t2 = heat_up_watrt('***我是烧水员***')
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    end_time = time.time()
    print('总共耗时:{}'.format(end_time-start_time))
    

    返回结果:

    我要拖地了
    我要烧水了
    ***我是烧水员***
    ***我是烧水员***
    地拖完了
    水烧开了
    总共耗时:6.000684022903442
    

    python的threading.Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时,就会调用run方法执行线程。

    当然你也可以在类方法中加上使用线程锁

    class test(threading.Thread):
    	def __init__(self,lock):
    		super().__init()__()
    		self.lock = lock
    
    	def run(self):
    		self.lock.acquire()
    		pass
    		self.lock.release()
    
    	if __name__ == '__main__':
    		lock = threading.Lock()
    		t = test(lock)
    

    run方法与start方法的区别

    其实不仅仅是start,使用run也可以让子线程执行:

    def mop_floor():
        print('我要拖地了')
        time.sleep(1)
        print('地拖完了')
    
    
    def heat_up_watrt():
        print('我要烧水了')
        time.sleep(6)
        print('水烧开了')
    
    start_time = time.time()
    t1 = threading.Thread(target=heat_up_watrt)
    t2 = threading.Thread(target=mop_floor)
    t1.run()
    t2.run()
    # 注意这里不能加上join()
    end_time = time.time()
    print('总共耗时:{}'.format(end_time - start_time))
    

    返回结果:

    我要烧水了
    水烧开了
    我要拖地了
    地拖完了
    总共耗时:7.000263929367065
    

    两个子线程都用run()方法启动,但却是先运行t1.run(),运行完之后才按顺序运行t2.run(),两个线程都工作在主线程,没有启动新线程,因此,run()方法仅仅是普通函数调用。

    start() 方法是启动一个子线程,线程名就是我们定义的name
    run() 方法并不启动一个新线程,就是在主线程中调用了一个普通函数而已。
    

    因此,如果你想启动多线程,就必须使用start()方法。

    烧水和拖地只是两个比喻,运用到实际业务中就好比爬虫中一个线程去爬标题与网址另一个去爬标题内的内容,也好比一个线程去获取网页内容,另一个线程去把网页内容写入本地,网络编程中也是,你在等待tcp链接的时候可以去做另外的事情等等。

    主线程与子线程

    回到刚刚烧水与拖地的例子中:

    主线程:一个进程中至少有一个线程,并作为程序的入口,这个线程就是主线程(从一开始的代码执行到最后的打印出执行时间为主线程)

    子线程:一个进程至少有一个主线程,其它线程称为子线程(拖地与烧水两个子线程)

    上面的例子中可以发现一共有三个线程,一个主线程和两个子线程,如何定义子线程的?其实在代码中就发现了,使用t1 = threading.Thread(target=heat_up_watrt)即可生成一个子线程,然后使用t1.start()即可启动这个子线程,这样的话t1.jion()是不是就多余呢?其实不然,使用t1.jion()的作用就是:等待子线程执行完毕后再执行主线程,如果不加上t1.jion()的话,子线程任然执行,但是子线程再等待的时候(io操作的时候),释放出资源,这个时候主线程拿到资源运行主线程的任务,就会直接打印出共耗时:xxx,然后再等待子线程运行结束,最后退出主程序。

    注意这里的t1.jion()放到位置很重要:

    t1.start()
    t1.join()
    t2.start()
    t2.join()
    

    如果这样放的话,就是先执行线程1然后等待线程1执行完毕,然后执行线程2等待线程2执行完毕。

    t1.start()
    t2.start()
    t1.join()
    t2.join()
    

    这样的话就不一样,会等到线程1与线程2执行完毕后再执行主线程。

    上面说到主线程推出后要等待子线程执行完毕后才会退出整个主程序,此时使用t1.setDaemon(True)的话,会当主线程执行完毕后,t1子线程不管有没有执行完毕,都退出主程序。

    线程间通信

    在一个进程中,不同子线程负责不同的任务,t1子线程负责获取到数据,t2子线程负责把数据保存的本地,那么他们之间的通信使用Queue来完成。因为再一个进程中,数据变量是共享的,即多个子线程可以对同一个全局变量进行操作修改,Queue是加了锁的安全消息队列。

    在此之前回顾Queue消息队列的使用Queue消息队列
    注意queue.join()阻塞等待队列中任务全部处理完毕,需要配合queue.task_done使用

    import threading
    import time
    import queue
    
    q = queue.Queue(maxsize=5)
    
    def t1(q):
        while 1:
            for i in range(10):
                q.put(i)
    
    
    
    
    def t2(q):
        while not q.empty():
            print('队列中的数据量:'+str(q.qsize()))
            # q.qsize()是获取队列中剩余的数量
            print('取出值:'+str(q.get()))
            # q.get()是一个堵塞的,会等待直到获取到数据
            print('-----')
            time.sleep(0.1)
    
    
    t1 = threading.Thread(target=t1,args=(q,))
    t2 = threading.Thread(target=t2,args=(q,))
    t1.start()
    t2.start()
    

    返回结果:

    队列中的数据量:5
    取出值:0
    -----
    队列中的数据量:5
    取出值:1
    -----
    队列中的数据量:5
    取出值:2
    -----
    队列中的数据量:5
    取出值:3
    -----
    队列中的数据量:5
    取出值:4
    -----
    队列中的数据量:5
    取出值:5
    

    这个即使消息队列的简单使用,举个例子就能清晰的明白线程间使用queue如何通信:

    import threading
    import time
    import queue
    '''
    模拟包子店卖包子
    厨房每一秒钟制造一个包子
    顾客每三秒吃掉一个包子
    厨房一次性最多存放100个包子
    '''
    q = queue.Queue(maxsize=100)
    # 厨房一次性最多存放100个包子
    
    def produce(q):
    # 这个函数专门产生包子
        for i in range(10000):
            q.put('第{}个包子'.format(str(i)))
            # 生产出包子,表明包子的id号
            time.sleep(1)
            # 要一秒才能造出一个包子
    
    
    def consume(q):
        while not q.empty():
            # 只要包子店里有包子
            print('包子店的包子剩余量:'+str(q.qsize()))
            # q.qsize()是获取队列中剩余的数量
            print('小桃红吃了:'+str(q.get()))
            # q.get()是一个堵塞的,会等待直到获取到数据
            print('------------')
            time.sleep(3)
    
    
    t1 = threading.Thread(target=produce,args=(q,))
    t2 = threading.Thread(target=consume,args=(q,))
    t1.start()
    t2.start()
    

    返回结果:

    包子店的包子剩余量:1
    小桃红吃了:第0个包子
    ------------
    包子店的包子剩余量:2
    小桃红吃了:第1个包子
    ------------
    包子店的包子剩余量:4
    小桃红吃了:第2个包子
    ------------
    包子店的包子剩余量:6
    小桃红吃了:第3个包子
    ------------
    包子店的包子剩余量:8
    小桃红吃了:第4个包子
    ------------
    包子店的包子剩余量:10
    小桃红吃了:第5个包子
    ------------
    ......
    

    线程同步

    如果没有控制多个线程对同一资源的访问,对数据造成破坏,使得线程运行的结果不可预期。这种现象称为“线程不安全”。

    同步就是协同步调,按预定的先后次序进行运行。

    如:你说完,我再说。

    "同"字从字面上容易理解为一起动作

    其实不是,"同"字应是指协同、协助、互相配合。

    如进程、线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。

    线程锁实现同步控制

    线程锁使用threading.Lock()实例化,使用acquire()上锁,使用release()释放锁,牢记acquire与release()必须要同时成对存在。它提供一些如下方法:

    acquire():上锁,这个时候只能运行上锁后的代码
    release():解锁,解锁后把资源让出来,给其他线程使用
    

    举个例子:

    def run1():
        while 1:
            print('我是老大,我先运行')
    def run2():
        while 1:
            print('我是老二,我第二运行')
    def run3():
        while 1:
            print('我是老三,我最后运行')
    
    t1 = threading.Thread(target=run1)
    t2 = threading.Thread(target=run2)
    t3 = threading.Thread(target=run3)
    t1.start()
    t2.start()
    t3.start()
    

    这样运行的结果是无序没法控制的,但是当你加上一把锁后,就不一样了。

    def run1():
        while 1:
            if l1.acquire():
                # 如果第一把锁上锁了
                print('我是老大,我先运行')
                l2.release()
                # 释放第二把锁
    def run2():
        while 1:
            if l2.acquire():
                # 如果第二把锁上锁了
                print('我是老二,我第二运行')
                l3.release()
                # 释放第三把锁
    
    def run3():
        while 1:
            if l3.acquire():
                # 如果第三把锁上锁了
                print('我是老三,我最后运行')
                l1.release()
                # 释放第一把锁
    
    
    t1 = threading.Thread(target=run1)
    t2 = threading.Thread(target=run2)
    t3 = threading.Thread(target=run3)
    
    l1 = threading.Lock()
    l2 = threading.Lock()
    l3 = threading.Lock()
    # 实例化三把锁
    
    l2.acquire()
    l3.acquire()
    
    t1.start()
    t2.start()
    t3.start()
    

    返回结果:

    我是老大,我先运行
    我是老二,我第二运行
    我是老三,我最后运行
    我是老大,我先运行
    我是老二,我第二运行
    我是老三,我最后运行
    我是老大,我先运行
    我是老二,我第二运行
    我是老三,我最后运行
    .....
    

    此时虽然按照自己的要求同步执行,但是运行速度会慢一点,因为上锁与释放锁需要时间会影响性能。

    lock还有Rlock的方法,RLock允许在同一线程中被多次acquire(比如你一个函数上了锁,这个函数调用另一个函数,另一个函数也上了锁 )。而Lock却不允许这种情况。否则会出现死循环,程序不知道解哪一把锁。注意:如果使用RLock,那么acquire和release必须成对出现,即调用了n次acquire,必须调用n次的release才能真正释放所占用的锁

    条件变量实现同步精准控制

    条件变量,用于复杂的线程间同步。在一些对线程间通信要求比较精准的需求下,使用简单的lock加锁解锁已经没法实现需求,这个时候condition条件控制就派上用场了。

    condition源代码中本质上还是调用lock实现条件变量的控制, 他提供如下一些方法:

    acquire():上锁
    release():解锁
    wait(timeout=None):堵塞线程,知道接受到一个notify或者超时才能继续运行,需记住wait()必须在已经获得lock的前提下才能调用
    notify(n=1):打通线程(个人觉得这样叫方便理解),堵塞的线程接收到notify后开始运行,需记住notify()必须在已经获得lock的前提下才能调用
    notifyAll():如果调用wait()堵塞的线程比较多,就打通所有的堵塞线程
    

    通过实例代码来弄清楚他们怎么调用:

    import threading
    import random
    import queue
    
    q = queue.Queue(maxsize=100)
    
    def produce(q):
        while 1:
            result = str(random.randint(1,100))
            q.put(result)
            print('我生成了一个随机数字:'+result)
    def consume(q):
        while 1:
            res = q.get()
            print('我获取到了你生成的随机数字:'+str(res))
    
    t1 = threading.Thread(target=produce,args=(q,))
    t2 = threading.Thread(target=consume,args=(q,))
    t1.start()
    t2.start()
    

    返回结果:

    我生成了一个随机数字:29
    我获取到了你生成的随机数字:92
    我生成了一个随机数字:38
    我获取到了你生成的随机数字:9
    我生成了一个随机数字:21
    我获取到了你生成的随机数字:37
    我生成了一个随机数字:28
    

    由于线程的不安全性,每次生成和获取的数字都并非同时是按顺序索取要得到的,这个时候condition就派上用场了(其实如果设置消息队列的q=queue.Queue(size=1)就能解决这个问题)。

    import threading
    import random
    
    def produce():
        global q
        while 1:
            con.acquire()
            # 必须在有锁的前提下才能使用条件变量
            q = str(random.randint(1,100))
            print('我生成了一个随机数字:'+q)
            con.notify()
            # 发起一个信号,释放一个被堵塞的线程
            con.wait()
            # 发起一个信号,堵塞当前线程,等待另一个notify出现的时候就执行下面的代码
            con.release()
            # 必须要解锁
    def consume():
        global q
        while 1:
            con.acquire()
            # 必须在有锁的前提下才能使用条件变量
            print('我获取到你生成的随机数字:'+q)
            con.notify()
            # 发起一个信号,释放一个被堵塞的线程
            con.wait()
            # 堵塞当前线程
            con.release()
    
    t1 = threading.Thread(target=produce)
    t2 = threading.Thread(target=consume)
    con = threading.Condition()
    t1.start()
    t2.start()
    

    返回结果:

    我生成了一个随机数字:99
    我获取到你生成的随机数字:99
    我生成了一个随机数字:53
    我获取到你生成的随机数字:53
    我生成了一个随机数字:26
    我获取到你生成的随机数字:26
    

    同时condition的源码类中是有enter和exit两个魔法函数的,这也就意味能够使用with上下管理,省去自己加锁解锁的过程,举一个更加方便理解的例子:

    import threading
    import time
    con = threading.Condition()
    def run():
        while 1:
            with con:
                # 使用上下文管理器,省去了自动上锁解锁的过程
                print('-----------------')
                print('我完事了,该你了')
                con.notify()
                # 发起一个信号,释放掉一个wait
                con.wait()
    
    
    def result():
        while 1:
            with con:
                con.wait()
                # 我在等待一个noity出现,这样我就能运行了
                time.sleep(0.3)
                print('三秒后....')
                print('我也完事了,你继续')
                con.notify()
    
    t1 = threading.Thread(target=run)
    t2 = threading.Thread(target=result)
    t2.start()
    t1.start()
    # 注意这里必须t2先运行,想想为什么
    

    返回结果:

    -----------------
    我完事了,该你了
    三秒后....
    我也完事了,你继续
    -----------------
    我完事了,该你了
    三秒后....
    我也完事了,你继续
    ......
    

    condition源码中其实是调用了两把锁,外层的锁是调用lock,内层的锁是通过wait()方法实现,每次调用wait的时候,都会分配一把锁放在等待的队列中,知道noitfy方法出现就释放这把锁。

    信号量实现定量的线程同步

    semaphore适用于控制进入数量的锁,好比文件的读写操作,写入的时候一般只用一个线程写,如果多个线程同时执行写入操作的时候,就会造成写入数据混乱。 但是读取的时候可以用多个线程来读取,可以看到写与写是互斥的,读与写不是互斥的,读与读不是互斥的。

    文件读写只是个例子,在一些日常业务中比如爬虫读取网址的线程数量控制等。

    BoundedSemaphore。这种锁允许一定数量的线程同时更改数据,它不是互斥锁。比如地铁安检,排队人很多,工作人员只允许一定数量的人进入安检区,其它的人继续排队。

    这个使用很简单的:

    import time
    import threading
    
    def run(n, se):
        se.acquire()
        print("run the thread: %s" % n)
        time.sleep(1)
        se.release()
    
    # 设置允许5个线程同时运行
    semaphore = threading.BoundedSemaphore(5)
    for i in range(20):
        t = threading.Thread(target=run, args=(i,semaphore))
        t.start()
    

    运行后,可以看到5个一批的线程被放行。他用来控制进入某段代码的线程数量。

    最后提起一点,Rolok是基于lock实现了,condition是基于Rlock和lock实现的,semahhore是基于condition实现的。

    事件实现线程锁同步

    事件线程锁的运行机制:
    全局定义了一个Flag,如果Flag的值为False,那么当程序执行wait()方法时就会阻塞,如果Flag值为True,线程不再阻塞。这种锁,类似交通红绿灯(默认是红灯),它属于在红灯的时候一次性阻挡所有线程,在绿灯的时候,一次性放行所有排队中的线程。

    事件主要提供了四个方法set()、wait()、clear()和is_set()。

    调用wait()方法将等待信号。
    is_set():判断当前是否状态
    调用set()方法会将Flag设置为True。
    调用clear()方法会将事件的Flag设置为False。
    

    举个例子:

    import threading
    import time
    import random
    
    boys = ['此时一位捡瓶子的靓仔路过\n------------','此时一位没钱的网友路过\n------------','此时一位推着屎球的屎壳郎路过\n------------']
    event = threading.Event()
    def lighter():
        event.set()
        while 1:
            ti = (random.randint(1, 10))
            time.sleep(ti)
            print('等待 {} 秒后'.format(str(ti)))
            event.clear()
            time.sleep(ti)
            event.set()
    
    
    def go(boy):
        while 1:
            if event.is_set():
                # 如果事件被设置
                print('在辽阔的街头')
                print(boy)
                time.sleep(random.randint(1, 5))
            else:
                print('在寂静的田野')
                print(boy)
                event.wait()
                print('突然,一辆火车驶过')
                time.sleep(5)
    
    t1 = threading.Thread(target=lighter)
    t1.start()
    
    for boy in boys:
        t2 = threading.Thread(target=go,args=(boy,))
        t2.start()
    

    线程池编程

    作用

    线程池只有在py3.2才内置的,2版本只能自己维护一个线程池很麻烦。在Python3.2中的concurrent_futures,其可以实现线程池,进程池,不必再自己使用管道传数据造成死锁的问题。并且这个模块具有线程池和进程池、管理并行编程任务、处理非确定性的执行流程、进程/线程同步等功能,但是平时用的最多的还是用来构建线程池和进程池。

    在线程池中,主线程可以获取任意一个线程的状态以及返回结果,并且当一个线程完成后,主线程能立即得到结果。

    功能

    此模块由以下部分组成:

    1. concurrent.futures.Executor: 这是一个虚拟基类,提供了异步执行的方法。
    2. submit(function, argument): 调度函数(可调用的对象)的执行,将 argument 作为参数传入。
    3. map(function, argument): 将 argument 作为参数执行函数,以 异步 的方式。
    4. shutdown(Wait=True): 发出让执行者释放所有资源的信号。
    5. concurrent.futures.Future: 其中包括函数的异步执行。Future对象是submit任务(即带有参数的functions)到executor的实例
    6. done():比如t1.done()判断任务t1是否完成,没完成则返回False
    7. cancel():比如t1.cancel(),取消线程执行,当线程正在执行和执行完毕后是没法取消执行的,但是如果一个线程没有启动的话,是可以取消t1线程执行的

    套用模板

    线程池用起来非常方便,基本上是照着模板往里面套即可

    	# coding:utf-8
    	from concurrent.futures import ThreadPoolExecutor
    	# 导入线程池模块
    	import threading
    	# 导入线程模块,作用是获取当前线程的名称
    	import os,time
    	
    	def task(n):
    	    print('%s:%s is running' %(threading.currentThread().getName(),os.getpid()))
    	    # 打印当前线程名和运行的id号码
    	    time.sleep(2)
    	    return n**2
    	    # 返回传入参数的二次幂
    	
    	if __name__ == '__main__':
    	    p=ThreadPoolExecutor()
    	    #实例化线程池,设置线程池的数量,不填则默认为cpu的个数*5
    	    l=[]
    	    # 用来保存返回的数据,做计算总计
    	    for i in range(10):
    	        obj=p.submit(task,i)
    			# 这里的obj其实是futures的对象,使用obj.result()获取他的结果
    	        # 传入的参数是要执行的函数和该函数接受的参数
    			# submit是非堵塞的
    			# 这里执行的方式是异步执行
    			# -----------------------------------
    			# # p.submit(task,i).result()即同步执行
    			# -----------------------------------
    			# 上面的方法使用range循环有个高级的写法,即map内置函数
    			# p = ThreadPoolExecutor()
    			# obj=p.map(task,range(10))
    			# p.shutdown()
    			# 这里的obj的值就是直接返回的所有计算结果,不属于futures对象
    			# -----------------------------------
    	        l.append(obj)
    	        # 把返回的结果保存在空的列表中,做总计算
    	    p.shutdown()
    	    # 所有计划运行完毕,关闭结束线程池
    	    
    	    print('='*30)
    	    print([obj.result() for obj in l])
    	
    	#上面方法也可写成下面的方法
    	    # with ThreadPoolExecutor() as p:   #类似打开文件,可省去.shutdown()
    	    #     future_tasks = [p.submit(task, i) for i in range(10)]
    	    # print('=' * 30)
    	    # print([obj.result() for obj in future_tasks])
    

    submit与map的区别

    1. submit返回的是一个futures的对象,使用.result()才能获取他的运行结果
    2. submit接受的对象是函数加一个固定的参数
    3. map返回的是所有线程执行完毕后返回的结果
    4. map接受的对象是函数加一个传入函数的集合列表
    5. 他们都能提前获取先执行完的结果
    6. map比submit简单好用
    7. map返回的结果是安装列表传入参数的顺序返回结果,submit返回结果是哪个线程先执行完就返回哪个线程的结果

    获取部分执行完成的结果

    想要获取到部分执行完成的结果使用

    from concurrent.futures import as_completed
    

    as_completed其实是一个生成器,他只会返回以及完成的线程结果

    比如:

    all_tasks = [p.submit(task,obj) for obj in l]
    for f in as_completed(all_tasks):
    	data = f.result()
    

    因为生成器的缘故,他会先把执行完毕的结果赋值给data,其实通过map方法也可以实现

    for data in p.map(task,range(10)):
    	print data
    

    这样也是可以优先把执行完毕的结果获取出来,map函数中其实也有yield函数。map和as_completed的区别在于,map执行是按照传入列表的元素一个一个按顺序返回的,并且返回的是直接的结果,as_completed返回的是一个futures的对象,使用.result()获取他的结果。as_completed返回的不是按照顺序返回的。

    堵塞线程

    多线程中使用jion堵塞线程,在线程池中也可以堵塞等待某一些子线程的线程池执行完毕后再执行主线程

    from concurrent.futures import wait
    

    wait用在你的线程池下面,比如:

    all_tasks = [p.submit(task,obj) for obj in l]
    wait(all_tasks)
    

    这里只有等待all_tasks里面的线程执行完毕后才能继续执行,里面的可以加上参数等待线程池中只要有一个线程执行结束就执行后面的代码

    wait(all_tasks,return_when=FIRST_COMPLETED)
    

    判断是否执行完毕

    task1 = p.submit(task,10)
    print(task1.done())
    # 如果执行完毕,返回True
    

    取消执行线程

    task1 = p.submit(task,10)
    task1.cancel()
    

    当线程正在执行和执行完毕后是没法取消执行的,但是如果一个线程没有启动的话,是可以取消t1线程执行的。

    线程池提供的方法非常简单,易于使用,但是他背后的原理是很值得研究的。

    线程池设计理念

    线程池优秀的设计理念在于:他返回的结果并不是执行完毕后的结果,而是futures的对象,这个对象会在未来存储线程执行完毕的结果,这一点也是异步编程的核心。python为了提高与统一可维护性,多线程多进程和协程的异步编程都是采取同样的方式。

    多进程

    因为GIL的存在,没法利用到多核CPU的优势,这个时候多进程就出来了,它能利用多个CPU并发的优势实现并行。

    多进程:消耗CPU操作,CPU密集计算

    多线程:大量的IO操作

    进程间切换的开销要大于线程间开销,对操作系统来说开多线程比开多进程来说消耗的资源少一些,不同的进程是由主进程完全复制后,每个进程独立隔离开,不像多线程对全局变量直接修改,因为是完全复制独立的,所以当主进程结束后,子进程任然执行。

    另外多进程必须要注意的一点就是在运行的时候,必须要加上

    if __name__ == '__main__':
    	pass
    

    进程池套用模板

    与线程池用法几乎一致,进程池是基于multiprocessing实现的

    from concurrent.futures import ProcessPoolExecutor
    import os,time,random
    def task(n):
        print('%s is running' %os.getpid())
        time.sleep(2)
        return n**2
    
    
    if __name__ == '__main__':
        p=ProcessPoolExecutor()  #不填则默认为cpu的个数
        l=[]
        start=time.time()
        for i in range(10):
            obj=p.submit(task,i)   #submit()方法返回的是一个future实例,要得到结果需要用obj.result()
            l.append(obj)
    
        p.shutdown()  #类似用from multiprocessing import Pool实现进程池中的close及join一起的作用
        print('='*30)
        # print([obj for obj in l])
        print([obj.result() for obj in l])
        print(time.time()-start)
    
        #上面方法也可写成下面的方法
        # start = time.time()
        # with ProcessPoolExecutor() as p:   #类似打开文件,可省去.shutdown()
        #     future_tasks = [p.submit(task, i) for i in range(10)]
        # print('=' * 30)
        # print([obj.result() for obj in future_tasks])
        # print(time.time() - start)
    

    进程池相关的使用功能与线程池基于一致。

    multiprocessing具体使用与数据通信

    这些功能在我之前的笔记写的比较完整与友好,这里不重复造轮子写概念了,
    具体传送Python multiprocess 多进程模块

    参考链接

    具体链接之线程池进程池

    参考链接 1

    参考链接 2

    参考链接 3

    欢迎关注公众号:【安全研发】获取更多相关工具,课程,资料分享哦~在这里插入图片描述

    展开全文
  • python多进程与并发

    万次阅读 2020-07-13 04:19:35
    介绍多线程与多进程的区别,以及python实现并发

    文章目录

    介绍

    • 多线程与多进程

      进程就是一个程序在一个数据集上的一次动态执行过程。

      线程的出现是为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷,使到进程内并发成为可能。

      通常是单个进程里包含多个线程,cpu上运行的是线程,但是python因为GUI锁导致单个进程只有一个线程,故python里没有多线程一说。

    • 并行与并发

      有些硬件只有单核处理,然而为了同时运行多个程序(例如同时打开latex和markdown软件)又不因只能按顺序执行而浪费资源故引入切换,即多个进程频繁切换产生同时运行的假象,即并发。

      并行是指多个线程同时运行,不用切换。

    代码实现

    • Thread类创建

      import threading
      import time
      
      def countNum(n): # 定义某个线程要运行的函数
      
          print("running on number:%s" %n)
      
          time.sleep(3)
      
      if __name__ == '__main__':
      
          t1 = threading.Thread(target=countNum,args=(23,)) #生成一个线程实例
          t2 = threading.Thread(target=countNum,args=(34,))
      
          t1.start() #启动线程
          t2.start()
      
          print("ending!")
      
    展开全文
  • Python多进程2 多进程的参数返回

    千次阅读 2020-01-15 21:23:02
    原文地址 学习来源 对于在当前主进程的操作,函数的返回值...在主进程中可以将他们依次取出,这样就做到了多进程与主进程的返回值传递。关于队列,我在我的这篇博客中做了简单说明,该博客也是多线程中返回值传递的...

    原文地址

    学习来源

    分类目录——多进程

    对于在当前主进程的操作,函数的返回值可以直接操作,或者用一个参量进行接收。但是在其他进程中运行的函数的返回值,是无法直接传递到主进程的。将其返回值存到一个全局性的存储器中,是一种可行的方案。这里用queue(队列)来存储多个进程的返回值。在主进程中可以将他们依次取出,这样就做到了多进程与主进程的返回值传递。关于队列,我在我的这篇博客中做了简单说明,该博客也是多线程中返回值传递的介绍。

    实例演示

    import multiprocessing as mp    # 多进程的模块
    
    def job(q):     # 进程调用测程序,传递的参数为一个queue(队列)对象
        res = 0
        for i in range(1000):
            res += i**2
        q.put(res)      # 把结果(就是主进程中函数的返回值)存入队列
    
    if __name__ == '__main__':
        q = mp.Queue()
        p1 = mp.Process(target=job, args=(q,))
        # 生成进程,并传入要执行的操作(函数),args=参数列表,只传一个参数的时候注意要在参数后面加一个逗号,因为args需要一个可迭代的参量
        p2 = mp.Process(target=job, args=(q,))
        p1.start()      # 进程操作开始执行
        p2.start()
        p1.join()       # 将进程操作设定为关键路径
        p2.join()
        # p1,p2都进行join()操作,p1,p2执行完之后(p1,p2都进行了job并把结果存入了q)才进行下面的操作
        res1 = q.get()      # 从结果队列中取值
        res2 = q.get()
        print(res1+res2)
    
    展开全文
  • Python多进程之进程池

    千次阅读 2017-04-05 11:28:16
    当我们有并行处理需求的时候,可以采用多进程迂回地解决。如果要在主进程中启动大量的子进程,可以用进程池的方式批量创建子进程。 首先,创建一个进程池子,然后使用apply_async()方法将子进程加入到进程池中。...
  • gdb调试多进程

    千次阅读 2020-12-01 11:15:28
    在Linux系统下主要使用的调试器就是gdb, 但是在使用gdb的时候遇到了多进程的程序时,默认的配置我们是不可以直接多多个进程同时进行调试的, 需要设置几个参数… 例子 这里程序利用fork()函数生成了一个子进程(第一个...
  • Python多进程之进程池Pool

    千次阅读 2018-04-12 17:26:53
    我们可以很方便的开启多进程,但是多进程开启还是需要限制一下的,否则会导致计算机崩溃 from multiprocessing import Process,Pool,freeze_support #导入相关的包,多进程和进程池 import time,os def Foo(i):...
  • 多进程和多线程的区别是什么?多进程和多线程的优缺点分析    多进程和多线程的区别是什么?此前小编给大家介绍了进程和线程的区别,那么大家知道多进程和多线程的区别又是什么吗?它们分别有什么优缺点?为了...
  • Python多进程多线程测试

    万次阅读 2019-09-17 17:18:35
    今天在工作中遇到爬虫效率问题,在此处记录多进程、多线程测试脚本 #!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = 'Seven' from concurrent.futures import ThreadPoolExecutor, ...
  • 多进程多进程间通信

    千次阅读 2015-03-17 07:55:18
    一、进程空间和多进程父子关系: 进程是分配资源的最小单位(进程内存空间)。fork后,子进程会复制父进程的task_struct结构;并为子进程的堆栈分配物理页(开始只是标记为只读的),延迟到当父或子线程尝试写这些区域...
  • 线程与线程之间共享全局变量,进程之间不能共享全局变量。 进程与进程相互独立 (可以通过socket套接字实现进程间通信,可以通过硬盘(文件)实现进程...demo.py(多进程): import threading # 线程 import tim...
  • 我在这里写了一些多线程的学习总结,在这里写了一些多进程的学习笔记,在这片文章中,就它们并串行执行的情况进行一下对比。 直接上程序 import multiprocessing as mp # 多进程的模块 import threading as td from ...
  • Android多进程实现,一个APP多个进程

    千次阅读 2019-07-30 10:03:06
    Android IPC机制(一)开启多进程(文章1:刘望舒大神的文章,1.3w阅读量,20赞) Android IPC机制(二)用Messenger进行进程间通信(文章2:刘望舒大神的文章,6k阅读量,7赞) Android多进程实现,一个APP多个...
  • 多线程与多进程

    千次阅读 2019-02-27 10:26:49
    多进程 多线程 优劣 数据共享、同步 数据是分开的:共享复杂,需要用IPC;同步简单 多线程共享进程数据:共享简单;同步复杂 各有优势 内存、CPU 占用内存多,切换复杂,CPU利用率低 占用内存少...
  • python多进程假死

    千次阅读 多人点赞 2019-08-23 15:55:17
    结论:python多进程间用Queue通信时,如果子进程操作Queue满了或者内容比较大的情况下,该子进程会阻塞等待取走Queue内容(如果Queue数据量比较少,不会等待),如果调用join,主进程将处于等待,等待子进程结束,造成...
  • python多进程和进程池

    千次阅读 2018-12-15 02:05:42
    写在最前面:   linux下可使用 fork ...我们分别使用单进程和多进程处理run函数 # -*- coding: utf-8 -*- import time,os from multiprocessing import Pool def run(n): time.sleep(1) print('Run chil...
  • c++多进程编程

    千次阅读 2020-04-16 10:12:52
    c++多进程编程 介绍 进程:进程是一个正在执行的程序,是向CPU申请资源的,进程之间数据相互独立,一个进程至少有一个线程。 线程:线程是进程中的单一的顺序控制流程也可以叫做最小控制单元,线程是进程中执行单元...
  • 多进程详细讲解

    千次阅读 2018-08-18 19:48:32
    一、什么是多进程 相信看过我其他博客的同学已经对多线程和多进程有一个简单的了解了,下面我们简单说一下多进程。我们知道在进行较多的I/O操作时候,比如socket server之类的可以用到多线程,那么什么时候用多进程...
  • 编写多进程程序 --多进程编程

    千次阅读 2015-06-28 01:47:17
     通过编写多进程程序,熟练掌握fork()、exec()、wait()和waitpid()等函数的使用,进一步理解在Linux中多进程编程的步骤。 实验内容  该实验有3个进程,其中一个为父进程,其余两个是该父进程创建的子进程,...
  • python的多线程不是真正的多线程,所以使用多进程来实现高并发,比如训练模型读取数据时,但是kill只会杀死相应的进程ID,由于真实环境下子进程太多,一个一个去kill太麻烦,下面实现了只需要杀死主进程id即可同时使...
  • 这里我们解决以下几个问题:多进程与多线程概念 多进程和单进程区别 python下通过multiprocessing调用多进程的基本方法
  • 1.怎样用多进程 Android多进程概念:一般情况下,一个应用程序就是一个进程,这个进程名称就是应用程序包名。我们知道进程是系统分配资源和调度的基本单位,所以每个进程都有自己独立的 资源和内存空间,别的进程...
  • Python多进程初识

    千次阅读 2018-04-12 17:06:12
    ''' io 操作不占用CPU 读文件 读内存等都是io操作 计算是占cpu python的多线程其实是假的多线程,...这个时候可以用多进程,进程之间也不需要锁的概念 适合io密集型的任务,例如同时操作多个文件,多个socket操作,...
  • 多进程中,每个进程都是独立的,各自持有一份数据,无法共享。本篇文章介绍三种用于进程数据共享的方法queuesArrayManager.dictpipeQueue12345678910111213from multiprocessing import queuesimport ...
  • python实现多进程

    万次阅读 多人点赞 2017-03-12 14:02:48
    在这里用Python程序来实现多进程并加深对多进程并发的理解
  • python 多进程编程

    万次阅读 2018-11-21 14:11:08
    python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。Python提供了非常好用的多进程包multiprocessing,只需要定义一个函数,Python会完成其他所有事情...
  • Linux多进程 -- 创建子进程

    千次阅读 2017-07-09 13:41:57
    Linux多进程 – 创建子进程 fork函数 Linux创建单个子进程 Linux创建多个子进程 父子进程共享内容
  • php多进程

    千次阅读 2016-11-08 09:35:54
    子进程要exit否则会进行递归多进程,父进程不要exit否则终止多进程for($i = 0; $i ; $i++){ $pid[$i] = pcntl_fork(); if($pid[$i] == -1){ }elseif($pid[$i]){ echo 'pcntl:start'; echo $pi
  • 多线程、多进程、线程池、进程池

    千次阅读 2017-04-11 15:01:18
    OS实现多进程和多线程往往是通过时间片的形式执行的,即让每个任务(进程/线程)轮流交替执行,因为时间片切分的很小,以至于我们感觉多个任务在同时执行。如果我们要同时执行多个任务怎么办?主要有两种解决方案: 一...
  • nginx 多进程进程 epoll

    千次阅读 2015-01-24 10:03:45
     有一个单进程的linux epoll服务器程序,近来希望将它改写成多进程版本,  主要原因有:  1、在服务高峰期间 并发的 网络请求非常大,目前的单进程版本的支撑不了:单进程时只有一个循环先后处理epoll_wait()...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 188,244
精华内容 75,297
关键字:

多进程