python----进程与线程

时间:2022-01-30 02:37:05

                                                                                               《进           程》

一:背景知识

顾名思义,进程即正在执行的一个过程。进程是对正在运行程序的一个抽象。

进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一。操作系统的其他所有内容都是围绕进程的概念展开的。

所以想要真正了解进程,必须事先了解操作系统。    

PS:即使可以利用的cpu只有一个(早期的计算机确实如此),也能保证支持(伪)并发的能力。将一个单独的cpu变成多个虚拟的cpu(多道技术:时间多路复用和空间多路复用+硬件上支持隔离),没有进程的抽象,现代计算机将不复存在。

二:必备的理论基础。

1:操作系统的作用:

                              (1):隐藏丑陋复杂的硬件接口,提供良好的抽象接口.

                               (2):管理,调度进程,并且将多个进程对硬件的竞争变得有序。

2:多道技术

               (1):针对单核,实现并发。

                 ps:现在的主机一般是多核,那么每个核都会利用多道技术,但是核与核之间没有使用多道技术切换这么一说;有4个cpu,运行于cpu1的某个程序遇到io阻塞,会等到io结束再重新调度,会被调度到4个cpu中的任意一个,具体由操作系统调度算法决定。

                 (2):时间上的复用(复用一个cpu的时间片)+空间上的复用(如内存中同时有多道程序)

三:进程

1:什么是进程:

                      进程:正在进行的一个过程,或一个任务。负责执行是cpu。

例:

 egon在一个时间段内有很多任务要做:python备课的任务,写书的任务,交女朋友的任务,王者荣耀上分的任务,  

    但egon同一时刻只能做一个任务(cpu同一时间只能干一个活),如何才能玩出多个任务并发执行的效果?

    egon备一会课,再去跟李杰的女朋友聊聊天,再去打一会王者荣耀....这就保证了每个任务都在进行中.

2进程与程序的区别。

程序仅仅只是一堆代码而已,而进程指的是程序的运行过程。

  举例:

想象一位有一手好厨艺的计算机科学家egon正在为他的女儿元昊烘制生日蛋糕。

他有做生日蛋糕的食谱,

厨房里有所需的原料:面粉、鸡蛋、韭菜,蒜泥等。

在这个比喻中:

    做蛋糕的食谱就是程序(即用适当形式描述的算法)

    计算机科学家就是处理器(cpu)

    而做蛋糕的各种原料就是输入数据

   进程就是厨师阅读食谱、取来各种原料以及烘制蛋糕等一系列动作的总和

 

现在假设计算机科学家egon的儿子alex哭着跑了进来,说:XXXXXXXXXXXXXX

科学家egon想了想,处理儿子alex蛰伤的任务比给女儿元昊做蛋糕的任务更重要,于是

计算机科学家就记录下他照着食谱做到哪儿了(保存进程的当前状态),然后拿出一本急救手册,按照其中的指示处理蛰伤。这里,我们看到处理机从一个进程(做蛋糕)切换到另一个高优先级的进程(实施医疗救治),每个进程拥有各自的程序(食谱和急救手册)。当蜜蜂蛰伤处理完之后,这位计算机科学家又回来做蛋糕,从他
离开时的那一步继续做下去。

  需要强调的是:同一个程序执行两次,那也是两个进程,比如打开暴风影音,虽然都是同一个软件,但是一个可以播放苍井空,一个可以播放饭岛爱。

 3:并发与并行

       无论是并行还是并发,在用户看来都是'同时'运行的,不管是进程还是线程,都只是一个任务而已,真是干活的是cpu,cpu来做这些任务,而一个cpu同一时刻只能执行一个任务

                       1: 并发:是伪并行,即看起来是同时运行。单个cpu+多道技术就可以实现并发,(并行也属于并发)

                       2:   二 并行:同时运行,只有具备多个cpu才能实现并行。

                        

                               单核下,可以利用多道技术,多个核,每个核也都可以利用多道技术(多道技术是针对单核而言的

                              有四个核,六个任务,这样同一时间有四个任务被执行,假设分别被分配给了cpu1,cpu2,cpu3,cpu4,一旦任务1遇到I/O就*中断执行,此时任务5就拿到cpu1的时间片去执行,这就是单核下的多道技术  而一旦任务1的I/O结束了,操作系统会重新调用它(需知进程的调度、分配给哪个cpu运行,由操作系统说了算),可能被分配给四个cpu中的任意一个去执行。

python----进程与线程

所有现代计算机经常会在同一时间做很多件事,一个用户的PC(无论是单cpu还是多cpu),都可以同时运行多个任务(一个任务可以理解为一个进程)。

    启动一个进程来杀毒(360软件)

    启动一个进程来看电影(暴风影音)

    启动一个进程来聊天(腾讯QQ)

  所有的这些进程都需被管理,于是一个支持多进程的多道程序系统是至关重要的

      多道技术:内存中同时存入多道(多个)程序,cpu从一个进程快速切换到另外一个,使每个进程各自运行几十或几百毫秒,这样,虽然在某一个瞬间,一个cpu只能执行一个任务,但在1秒内,cpu却可以运行多个进程,这就给人产生了并行的错觉,即伪并发,以此来区分多处理器操作系统的真正硬件并行(多个cpu共享同一个物理内存)

四:同步与异步

                      同步:一个进程在执行某个任务的时,其他进程必须等待其执行完毕,才能继续执行。

                      异步:一个进程在执行某个任务时,其他进程无需等待其执行完毕,就可以继续执行,当有消息返回时,系统通知后者进行处理,这样可以提高效率。

例:打电话时就是同步通信,发短息时就是异步通信。

五:进程的创建

   但凡是硬件,都需要有操作系统去管理,只要有操作系统,就有进程的概念,就需要有创建进程的方式,一些操作系统只为一个应用程序设计,比如微波炉中的控制器,一旦启动微波炉,所有的进程都已经存在。

  而对于通用系统(跑很多应用程序),需要有系统运行过程中创建或撤销进程的能力,主要分为4中形式创建新的进程

  1. 系统初始化(查看进程linux中用ps命令,windows中用任务管理器,前台进程负责与用户交互,后台运行的进程与用户无关,运行在后台并且只在需要时才唤醒的进程,称为守护进程,如电子邮件、web页面、新闻、打印)

  2. 一个进程在运行过程中开启了子进程(如nginx开启多进程,os.fork,subprocess.Popen等)

  3. 用户的交互式请求,而创建一个新进程(如用户双击暴风影音)

  4. 一个批处理作业的初始化(只在大型机的批处理系统中应用)

  

  无论哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的:

  1. 在UNIX中该系统调用是:fork,fork会创建一个与父进程一模一样的副本,二者有相同的存储映像、同样的环境字符串和同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程)

  2. 在windows中该系统调用是:CreateProcess,CreateProcess既处理进程的创建,也负责把正确的程序装入新进程。

 

  关于创建的子进程,UNIX和windows

  1.相同的是:进程创建后,父进程和子进程有各自不同的地址空间(多道技术要求物理层面实现进程之间内存的隔离),任何一个进程的在其地址空间中的修改都不会影响到另外一个进程。

  2.不同的是:在UNIX中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是可以有只读的共享内存区的。但是对于windows系统来说,从一开始父进程与子进程的地址空间就是不同的。

六:进程的终止

1. 正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)

  2. 出错退出(自愿,python a.py中a.py不存在)

  3. 严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等,可以捕捉异常,try...except...)

  4. 被其他进程杀死(非自愿,如kill -9)

七:进程的层次结构

无论UNIX还是windows,进程只有一个父进程,不同的是:

  1. 在UNIX中所有的进程,都是以init进程为根,组成树形结构。父子进程共同组成一个进程组,这样,当从键盘发出一个信号时,该信号被送给当前与键盘相关的进程组中的所有成员。

  2. 在windows中,没有进程层次的概念,所有的进程都是地位相同的,唯一类似于进程层次的暗示,是在创建进程时,父进程得到一个特别的令牌(称为句柄),该句柄可以用来控制子进程,但是父进程有权把该句柄传给其他子进程,这样就没有层次了。

八:进程的状态

tail -f access.log |grep '404'

  执行程序tail,开启一个子进程,执行程序grep,开启另外一个子进程,两个进程之间基于管道'|'通讯,将tail的结果作为grep的输入。

  进程grep在等待输入(即I/O)时的状态称为阻塞,此时grep命令都无法运行

  其实在两种情况下会导致一个进程在逻辑上不能运行,

  1. 进程挂起是自身原因,遇到I/O阻塞,便要让出CPU让其他进程去执行,这样保证CPU一直在工作

  2. 与进程无关,是操作系统层面,可能会因为一个进程占用时间过多,或者优先级等原因,而调用其他的进程去使用CPU。

  因而一个进程由三种状态

python----进程与线程

九:进程并发的实现

进程并发的实现在于,硬件中断一个正在运行的进程,把此时进程运行的所有状态保存下来,为此,操作系统维护一张表格,即进程表(process table),每个进程占用一个进程表项(这些表项也称为进程控制块)

python----进程与线程

 

该表存放了进程状态的重要信息:程序计数器、堆栈指针、内存分配状况、所有打开文件的状态、帐号和调度信息,以及其他在进程由运行态转为就绪态或阻塞态时,必须保存的信息,从而保证该进程在再次启动时,就像从未被中断过一样。

 生产者消费者模型

    在并发编程中使用生产者和消费者模型能够解决大多数并发问题。该模型通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据能力。

为什么要使用生产者和消费者模式

     在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理数据速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者必须等待正产者。为了解决这儿问题于是引入生产者消费者模式。

什么是生产者消费者模式(面试易考)

     生产者消费者模式是通过一个容器来解决生产者消费者的强耦合问题。生产者和消费者彼此间不能直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者 处理,直接扔给阻塞队列,消费不用找生生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡生产者和消费者的处理能力。

基于队列实现生产者消费者模型:

 

from multiprocessing import Process,Queue#引用m模块中的Process类,队列方法
import time#时间模块
import random#随机数模块
import os#与操作系统交互的模块
def consumer(q):#定义一个消费者函数
    while True:
        res=q.get()
        if res is None:
            break
        time.sleep(random.randint(1,3))
        print('\033[45m%s 吃了 %s\033[0m'%(os.getpid(),res))
def producer(q):#定义一个生产者函数
    for i in range(5):
        time.sleep(1)
        res='包子%s'%i
        q.put(res)#
        print('\033[44%s 制造 %s\033[0m'%(os.getpid(),res))
    q.put(None)
if __name__ == '__main__':
    q = Queue#队列
    p1 = Process(target=producer,args=(q,))
    p2 = Process(target=consumer,args=(q,))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print('')

 

进程池:

在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间。多进程是实现并发的手段之一,需要注意的问题是:

  1. 很明显需要并发执行的任务通常要远大于核数
  2. 一个操作系统不可能无限开启进程,通常有几个核就开几个进程
  3. 进程开启过多,效率反而会下降(开启进程是需要占用系统资源的,而且开启多余核数目的进程也无法做到并行)

例如当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个。。。手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。------------------------------通过维护一个进程池来控制进程数目(比如httpd的进程模式,规定最小进程数和最大进程数... )

 

ps:对于远程过程调用的高级应用程序而言,应该使用进程池,Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,就重用进程池中的进程

        (1):创建进程池的类:

                     如果指定numprocess为3,则进程池会从无到有创建三个进程,然后自始至终              使用这三个进程去执行所有任务,不会开启其他进程。

                    1 Pool([numprocess [,initializer [, initargs]]]):创建进程池 

        (2):参数介绍

1 numprocess:要创建的进程数,如果省略,将默认使用cpu_count()的值
         2 initializer:是每个工作进程启动时要执行的可调用对象,默认为None
         3 initargs:是要传给initializer的参数组
(3):方法介绍
1 p.apply(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。需要强调的是:此操作并不会在所有池工作进程中并执行func函数。如果要通过不同参数并发地执行func函数,必须从不同线程调用p.apply()函数或者使用p.apply_async()
        2 p.apply_async(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。此方法的结果是AsyncResult类的实例,callback是可调用对象,接收输入参数。当func的结果变为可用时,将理解传递给callback。callback禁止执行任何阻塞操作,否则将接收其他异步操作中的结果。
        3 p.close():关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成
        4 P.jion():等待所有工作进程退出。此方法只能在close()或teminate()之后调用
其他方法:

方法apply_async()和map_async()的返回值是AsyncResul的实例obj。实例具有以下方法
            obj.get():返回结果,如果有必要则等待结果到达。timeout是可选的。如果在指定时间内还没有到达,将引发一场。如果远程操作中引发了异常,它将在调用此方法时再次被引发。
            obj.ready():如果调用完成,返回True
            obj.successful():如果调用完成且没有引发异常,返回True,如果在结果就绪之前调用此方法,引发异常
            obj.wait([timeout]):等待结果变为可用。
            obj.terminate():立即终止所有工作进程,同时不执行任何清理或结束任何挂起工作。如果p被垃圾回收,将自动调用此函数
(4):应用
apply同步执行:阻塞式
#! usr/bin/env python
# -*- coding: utf-8 -*-
from multiprocessing import Pool
import os
import time
def work(n):
    print('%s run '%os.getpid())
    time.sleep(3)
    return n**2
if __name__ == '__main__':
    p = Pool(3)
    res_l = []
    for i in range(10):
        res = p.apply(work,args=(i,))#同步运行,阻塞,直到本次任务执行完毕res。
        res_l.append(res)
    print(res_l)

--------------------------------------------------
76 run 
9776 run 
800 run 
76 run 
9776 run 
800 run 
76 run 
9776 run 
800 run 
76 run 
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

 

                                                                                               《线             程》

线程

一:什么是线程

在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程

  线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程

      车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一个流水线

      进程,只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位

      多线程:(多个控制线程)的概念,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间,相当于一个车间内有多条流水线,都共用一个车间的资源。

  二:线程的创建开销小

         创建进程的开销远大于线程。---------------创建进程,需要创建一个内存空间。

                                                      ---------------创建线程,在进程里创建线程,无需再去开创内存                                                                          空间。

         进程之间是竞争关系,线程之间是协作关系。

三:线程与进程的区别

       1:线程共享创建它的进程的地址空间;进程有自己的地址空间。

       2:线程可以直接访问其进程的数据段;进程有它们自己的父进程数据段的副本。

       3:线程可以直接与进程的其他线程通信;进程必须使用进程间通信来与兄弟进程通信。

       4:新线程很容易创建;新进程需要复制父进程。

       5:线程可以对同一进程的线程进行相当大的控制;进程只能对子进程执行控制。

       6:对主线程的更改(取消、优先级更改等)可能会影响该进程的其他线程的行为;对父进程的更               改不会影响子进程。

四:为何要使用多线程

       多线程:在一个进程中开启多个线程(如果多个任务共用一块地址空间,那么必须在进程内开启多个线程) 

      1:多线程共享一个进程的地址空间。

      2:线程比进程更轻量级,线程比进程更容易创建可撤销,在许多操作系统中,创建一个线程              比创建一个进程要快10-100倍,在有大量线程需要动态和快速修改时,这一特性很有用。

     3:若多个线程都是cpu密集型的,那么并不能获得性能上的增加,但如果存在大量的计算和大           量I/O处理,拥有多个线程允许这些活动彼此重叠运行,加快程序执行的速度。

    4:在多cpu系统中,为了最大限度利用多核,可以开启多个线程,比开进程开销要小。------不适        用于Python

五:多线程的应用举例

  例:开启一个字处理软件进程,该进程肯定需要办不止一件事情,比如监听键盘输入,处理文               字,定时自动将文字保存到硬盘,这三个任务操作的都是同一块数据,因而不能用多进                   程。只能在一个进程里并发地开启三个线程,如果是单线程,那就只能是,键盘输入时,                不能处理文字和自动保存,自动保存时又不能输入和处理文字。

六:经典的线程模型。

                多个线程共享同一个进程的地址空间中的资源,是对一台计算机上多个进程的模拟,有时也称线程为轻量级的进程而对一台计算机上多个进程,则共享物理内存、磁盘、打印机等其他物理资源。多线程的运行也多进程的运行类似,是cpu在多个线程之间的快速切换。

python----进程与线程

       不同的进程之间是充满敌意的,彼此是抢占、竞争cpu的关系,如果迅雷会和QQ抢资源。而同一个进程是由一个程序员的程序创建,所以同一进程内的线程是合作关系,一个线程可以访问另外一个线程的内存地址,大家都是共享的,一个线程干死了另外一个线程的内存,那纯属程序员脑子有问题。

python----进程与线程

不同于进程,线程库无法利用时钟中断强制让出CPU,可以调用thread_yield运行线程自动放弃cpu,让另外一个线程运行。

线程通常是有益的,但是带来了不小程序设计难度,线程的问题是:

  1. 父进程有多个线程,那么开启的子线程是否需要同样多的线程

   如果是,那么附近中某个线程被阻塞,那么copy到子进程后,copy版的线程也要被阻塞吗,想一想nginx的多线程模式接收用户连接。

  2. 在同一个进程中,如果一个线程关闭了问题,而另外一个线程正准备往该文件内写内容呢?

          如果一个线程注意到没有内存了,并开始分配更多的内存,在工作一半时,发生线程切换,新的线程也发现内存不够用了,又开始分配更多的内存,这样内存就被分配了多次,这些问题都是多线程编程的典型问题,需要仔细思考和设计。

七:POSIX线程

 为了实现可移植的线程程序,IEEE在IEEE标准1003.1c中定义了线程标准,它定义的线程包叫Pthread。大部分UNIX系统都支持该标准,简单介绍如下

python----进程与线程

八:在用户空间实现线程

线程的实现可以分为两类:用户级线程(User-Level Thread)和内核线线程(Kernel-Level Thread),后者又称为内核支持的线程或轻量级进程。在多线程操作系统中,各个系统的实现方式并不相同,在有的系统中实现了用户级线程,有的系统中实现了内核级线程。

    用户级线程内核的切换由用户态程序自己控制内核切换,不需要内核干涉,少了进出内核态的消耗,但不能很好的利用多核Cpu,目前Linux pthread大体是这么做的。

python----进程与线程

      在用户空间模拟操作系统对进程的调度,来调用一个进程中的线程,每个进程中都会有一个运行时系统,用来调度线程。此时当该进程获取CPU时,进程内再调度出一个线程去执行,同一时刻只有一个线程执行。

九:在内核空间实现的线程

内核级线程:切换由内核控制,当线程进行切换的时候,由用户态转化为内核态。切换完毕要从内核态返回用户态;可以很好的利用smp,即利用多核cpu。windows线程就是这样的。

python----进程与线程

十:用户级于内核级,线程的对比。

  一: 以下是用户级线程和内核级线程的区别:

  1. 内核支持线程是OS内核可感知的,而用户级线程是OS内核不可感知的。
  2. 用户级线程的创建、撤消和调度不需要OS内核的支持,是在语言(如Java)这一级处理的;而内核支持线程的创建、撤消和调度都需OS内核提供支持,而且与进程的创建、撤消和调度大体是相同的。
  3. 用户级线程执行系统调用指令时将导致其所属进程被中断,而内核支持线程执行系统调用指令时,只导致该线程被中断。
  4. 在只有用户级线程的系统内,CPU调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行;在有内核支持线程的系统内,CPU调度则以线程为单位,由OS的线程调度程序负责线程的调度。
  5. 用户级线程的程序实体是运行在用户态下的程序,而内核支持线程的程序实体则是可以运行在任何状态下的程序。

       二: 内核线程的优缺点

  优点:

  1. 当有多个处理机时,一个进程的多个线程可以同时执行。

  缺点:

  1. 由内核进行调度。

      三: 用户进程的优缺点

  优点:

  1. 线程的调度不需要内核直接参与,控制简单。
  2. 可以在不支持线程的操作系统中实现。
  3. 创建和销毁线程、线程切换代价等线程管理的代价比内核线程少得多。
  4. 允许每个进程定制自己的调度算法,线程管理比较灵活。
  5. 线程能够利用的表空间和堆栈空间比内核级线程多。
  6. 同一进程中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起。另外,页面失效也会产生同样的问题。

  缺点:资源调度按照进程进行,多个处理机下,同一个进程中的线程只能在同一个处理机下复用。

十一:混合使用

用户级与内核级的多路复用,内核同一调度内核线程,每个内核线程对应n个用户线程。

python----进程与线程

  十二:threding模块介绍

multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性。

 

十三:开启线程的两种方式。

                            #1--------函数
from threading import Thread#调用模块threading下的Thread类(线程),
import time
def asyni(name):
    time.sleep(2)
    print('%s 你好! '%name)
if __name__ == '__main__':
    t = Thread(target=asyni,args=('悟空',))
    t.start()
    print('主线程')

#
2---------面向对象 from threading import Thread import time class Xiancheng(Thread): def __init__(self,name):#Python规定的动态属性 super().__init__() self.name = name def run(self): time.sleep(2) print('%s 你好! '%self.name) if __name__ == '__main__': t = Xiancheng('悟空') t.start() print('主线程')

十四:在一个进程下开启多个线程与在一个进程下开启对个子进程的区别。

1谁的开启速度快

from threading import Thread
from multiprocessing import Process
import os
import time
def work():
    print('hello')
if __name__ == '__main__':
    #在主进程下开启线程
    t = Thread(target=work)
    t.start()
    print('主进程/主线程')
    #在主进程下开启子进程
    t = Process(target=work)
    t.start()
    print('主进程/主线程')

--------------------------------------
hello
主进程/主线程
主进程/主线程
hello
2:在主进程下开启多个线程,每个线程跟主进程的pid一样  
  在主进程下开启多个进程,每个进程都有不同的Pid
from threading import Thread
from  multiprocessing import Process
import os
def work():
    print('hello',os.getpid())
if __name__ == '__main__':
    #在主进程下开启多个线程,每个线程跟主进程的pid一样
    t1 = Thread(target=work)
    t2 = Thread(target=work)
    t1.start()
    t2.start()
    print('主线程/主进程',os.getpid())
    #在主进程下开启多个进程,每个进程都有不同的Pid
    p1 = Process(target=work)
    p2 = Process(target=work)
    p1.start()
    p2.start()
    print('主进程/主进程',os.getpid())

3:同一进程内的线程共享该进程的数据

from threading import Thread
# from multiprocessing import Process
import os
def work():
    global n
    n = 0
if __name__ == '__main__':
    # n = 100
    # p = Process(target=work)
    # p.start()
    # p.join()
    # print('',n)
    #子进程p已经将自己的全局n改成了0,但改的仅仅是它自己的
    #查看父进程仍然是100
    t = Thread(target=work())
    t.start()
    t.join()
    print('',n)
    #查看结果为0,因为同一进程内的线程之间共享进程内的数据

十五:练习

练习一:

多线程并发的socket服务端

import threading
import socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind(('127.0.0.1',8090))
s.listen(5)
def action(conn):
    while True:
        data = conn.recv(1024)
        print(data)
        conn.send(data.upper())
if __name__ == '__main__':
    while True:
        conn,addr = s.accept()
        p = threading.Thread(target=action,args=(conn,))
        p.start()

客户端

import socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('127.0.0.1',8090))
while True:
    msg = input('请输入:').strip()
    if not msg:continue
    s.send(msg.encode('utf-8'))
    data = s.recv(1024)
    print(data)

练习二:View Code

from threading import Thread
msg_l = []
format_msg_l = []
def tack():
    while True:
        msg = input('请输入:')
        if not msg:continue
        msg_l.append(msg)
def format_msg():
    while True:
        if msg_l:
            res = msg_l.pop()
            format_msg_l.append(res.upper())
def save():
    while True:
        if format_msg_l:
            with open('a.txt','a',encoding='utf-8') as f:
                res = format_msg_l.pop()
                f.write('%s\n'%res)
if __name__ == '__main__':
    t1 = Thread(target=tack)
    t2 = Thread(target=format_msg)
    t3 = Thread(target=save)
    t1.start()
    t2.start()
    t3.start()

 

十六:线程相关的其他方法

1:Thread实例对象的方法
  isAlive(): 返回线程是否活动的。
  getName(): 返回线程名。
  setName(): 设置线程名。

2:threading模块提供的一些方法:
  threading.currentThread(): 返回当前的线程变量。
  threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
from threading import Thread
import threading
from multiprocessing import Process
import os

def work():
    import time
    time.sleep(3)
    print(threading.current_thread().getName())


if __name__ == '__main__':
    #在主进程下开启线程
    t=Thread(target=work)
    t.start()

    print(threading.current_thread().getName())
    print(threading.current_thread()) #主线程
    print(threading.enumerate()) #连同主线程在内有两个运行的线程
    print(threading.active_count())
    print('主线程/主进程')

    '''
    打印结果:
    MainThread
    <_MainThread(MainThread, started 140735268892672)>
    [<_MainThread(MainThread, started 140735268892672)>, <Thread(Thread-1, started 123145307557888)>]
    主线程/主进程
    Thread-1
    '''

主线程等待子线程结束

from threading import Thread
import time
def sayhi(name):
    time.sleep(2)
    print('%s say hello' %name)

if __name__ == '__main__':
    t=Thread(target=sayhi,args=('egon',))
    t.start()
    t.join()
    print('主线程')
    print(t.is_alive())
    '''
    egon say hello
    主线程
    False
    '''

 

十七:守护线程

            守护线程与守护进程的异同:

                                                      主线程会等到所有非守护线程结束,才销毁守护线程。

                                                      主进程的代码运行完毕,就会销毁守护进程,然后等非守                                                         护进程运行完毕,主进程结束

from threading import Thread
import time
def hvncj(name):
    time.sleep(2)
    print('%s 你好! '%name)
if __name__ == '__main__':
    t = Thread(target=hvncj,args=('悟空',))
    # t.setDaemon(True)#必须在t.start()之前设置
    t.start()
    print('')
    print(t.is_alive())
from threading import Thread
import time
def foo():
    print(123)
    time.sleep(2)
    print('end123')
def bar():
    print(456)
    time.sleep(3)
    print('end456')
t1 = Thread(target=foo)
t2 = Thread(target=bar)
t1.daemon=True
t1.start()
t2.start()
print('main--------------')

十八:Python GIlL(Global in interpreter Lock)全局解释器锁

定义:在CPython中,全局解释器锁是一个互斥锁,它可以防止多个对象的出现。

           本地线程同时执行Python的一组语言。这个锁是主要的

           因为CPython的内存管理不是线程安全的。(然而,由于GIL

           存在,其他的特性已经发展到依赖于它所执行的保证)

         1: 在Cpython解释器,同一进程下开启的多线程,通一时刻只能有一个线程执行,无法利用多核优势。

         2:GIL并不是Python的特性,它是在实现Python解释器(Cpython)时所引入的一个概念,Python完全不依赖于GIL。

         3:有了GIL的存在,同一时刻同一进程中只有一个线程被执行。python----进程与线程

python----进程与线程

(进程可以利用多核,但开销大。多线程开销小,但却无法利用多核优势。)

要解决上面问题,我们需要在几点上达成一致:

1:CPU到底是用来做计算的,还是用来做I/o的?

2:多CPU,意味着可以有多个核并行完成计算,所以多核提升的是计算机性能。

3:每个CPU一但遇到I/O阻塞,仍然需要等待,所以对多核I/O操作没什么用处

     例:一个工人相当于cpu,此时计算相当于工人在干活,I/O阻塞相当于为工人干活提供所需原              材料的过程,工人干活的过程中如果没有原材料了,则工人干活的过程需要停止,直到等待            原材料的到来。

           如果你的工厂干的大多数任务都要有准备原材料的过程(I/O密集型),那么你有再多的工      人,意义也不大,还不如一个人,在等材料的过程中让工人去干别的活,

   反过来讲,如果你的工厂原材料都齐全,那当然是工人越多,效率越高。

结论:

     1:对计算机来说,cpu越多越好,但对于I/O来说,再多的CPU也没用。

     2:当然对运行一个程序来说,随着cpu的增多执行效率肯定会有所提高(不管提高幅度多大,总会有所提高),这是因为一个程序基本上不会是纯计算或者纯I/O,所以我们只能相对的去看一个程序到底是计算密集型还是I/O密集型,从而进一步分析python的多线程到底有无用武之地

 

#分析:
我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:
方案一:开启四个进程
方案二:一个进程下,开启四个线程

#单核情况下,分析结果: 
  如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜
  如果四个任务是I/O密集型,方案一创建进程的开销大,且进程的切换速度远不如线程,方案二胜

#多核情况下,分析结果:
  如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜
  如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜

 
#结论:现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的

          -----------------------------------------------计算密集型:多进程效率高---------------------------------------------

rom multiprocessing import Process
from threading import Thread
import os,time
def work():
    res = 0
    for i in range(100000000):
        res*=i
if __name__ == '__main__':
    l = []
    print(os.cpu_count())#本机为4核
    start = time.time()
    for i in range(4):
        p = Process(target=work)#耗时5s
        p = Thread(target=work)#耗时18s
        l.append(p)
        p.start()
    for p in l:
        p.join()

----------------------------------------------------I/O密集型:多线程效率高-------------------------------------------

from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
    time.sleep(2)
    print('=====>')
if __name__ == '__main__':
    l = []
    print(os.cpu_count())
    start = time.time()
    for i in range(400):
        # p = Process(target=work)#耗时12秒,大部分时间耗费在创建上
        p = Thread(target=work)#耗时2秒
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop = time.time()
    print('run time is %s'%(stop-start))

应用:

多线程用于IO密集型:如socket,爬虫,web

多进程用于计算机密集型:如金融分析

十九:同步锁

注意:

1.分析Lock的同时一定要说明:线程抢的是GIL锁,拿到执行权限后才能拿到互斥锁Lock

2.使用join与加锁的区别:join是等待所有,即整体串行,而锁只是锁住一部分,即部分串行

3. 一定要看本小节最后的GIL与互斥锁的经典分析

GIL VS Lock

      Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需lock? 

   首先我们需要达成共识:锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据

         然后,我们可以得出结论:保护不同的数据就应该加不同的锁。

     最后,问题就很明朗了,GIL 与Lock是两把锁,保护的数据不一样,前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理,即Lock

过程分析:所有线程抢的是GIL锁,或者说所有线程抢的是执行权限

   线程1抢到GIL锁,拿到执行权限,开始执行,然后加了一把Lock,还没有执行完毕,即线程1还未释放Lock,有可能线程2抢到GIL锁,开始执行,执行过程中发现Lock还没有被线程1释放,于是线程2进入阻塞,被夺走执行权限,有可能线程1拿到GIL,然后正常执行到释放Lock。。。这就导致了串行运行的效果

既然是串行,那我们执行

  t1.start()

  t1.join

  t2.start()

  t2.join()

  这也是串行执行啊,为何还要加Lock呢,需知join是等待t1所有的代码执行完,相当于锁住了t1的所有代码,而Lock只是锁住一部分操作共享数据的代码。

因为Python解释器帮你自动定期进行内存回收,
你可以理解为python解释器里有一个独立的线程,
每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,
此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,
假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,
可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,
为了解决类似的问题,python解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动,这样就解决了上述的问题,
这可以说是Python早期版本的遗留问题。 

 

from threading import Thread
import os,time
def work():
    global n
    temp=n
    time.sleep(0.1)
    n=temp-1
if __name__ == '__main__':
    n=100
    l=[]
    for i in range(100):
        p=Thread(target=work)
        l.append(p)
        p.start()
    for p in l:
        p.join()

    print(n) #结果可能为99

锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁:

import threading

R=threading.Lock()

R.acquire()
'''
对公共数据的操作
'''
R.release()
from threading import Thread,Lock
import os,time
def work():
    global n
    lock.acquire()
    temp=n
    time.sleep(0.1)
    n=temp-1
    lock.release()
if __name__ == '__main__':
    lock=Lock()
    n=100
    l=[]
    for i in range(100):
        p=Thread(target=work)
        l.append(p)
        p.start()
    for p in l:
        p.join()

    print(n) #结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全

GIL锁与互斥锁综合分析:‘重点’

1.100个线程去抢GIL锁,即抢执行权限
2. 肯定有一个线程先抢到GIL(暂且称为线程1),然后开始执行,一旦执行就会拿到lock.acquire()
3. 极有可能线程1还未运行完毕,就有另外一个线程2抢到GIL,然后开始运行,但线程2发现互斥锁lock还未被线程1释放,于是阻塞,*交出执行权限,即释放GIL
4.直到线程1重新抢到GIL,开始从上次暂停的位置继续执行,直到正常释放互斥锁lock,然后其他的线程再重复2 3 4的过程

 二十:死锁现象与递归锁

            进程---------------------------也有死锁与递归锁

             死锁:是指两个或两个以上的进程或线程在执行过过程中,因争夺资源而造成的一种互相等待的现象,无外力作用,他们都将无法推进下去。(此时称系统处于死锁状态或系统产生了死锁,这些永远等待的进程称为死--------锁进程)

死锁现象:

#! usr/bin/env python
# -*- coding: utf-8 -*-
from threading import Thread,Lock
import time
mutexA = Lock()
mutexB = Lock()
class MyThread(Thread):#定义类
    def run(self):#定义方法
        self.func1()
        self.func2()
    def func1(self):
        mutexA.acquire()
        print('\033[30m%s 拿到A锁\033[0m'%self.name)
        mutexB.acquire()
        print('\033[35m%s 拿到B锁\033[0m' % self.name)
        mutexB.release()
        mutexA.release()
    def func2(self):
        mutexB.acquire()
        print('\033[35m%s 拿到B锁\033[0m' % self.name)
        time.sleep(2)
        mutexA.acquire()
        print('\033[30m%s 拿到A锁\033[0m' % self.name)
        mutexA.release()
        mutexB.release()
if __name__ == '__main__':
    for i in range(10):
        t = MyThread()
        t.start()
--------------------------------结果-----------------------------------

Thread-1 拿到A锁
Thread-1 拿到B锁
Thread-1 拿到B锁
Thread-2 拿到A锁

‘卡住了’

解决方法:-----------递归锁

                在Python中为了支持在同一线程中多次请求同一资源,Python提供了可重写入锁RLock。

     这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire(加锁)的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release(释放锁),其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:       

    mutexA = mutexB = threading.RLock():

一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止.
解决死锁:
#! usr/bin/env python
# -*- coding: utf-8 -*-
from threading import Thread,RLock
import time
mutexA = mutexB = RLock()
class MyThread(Thread):#定义类
    def run(self):#定义方法
        self.func1()
        self.func2()
    def func1(self):
        mutexA.acquire()
        print('\033[30m%s 拿到A锁\033[0m'%self.name)
        mutexB.acquire()
        print('\033[35m%s 拿到B锁\033[0m' % self.name)
        mutexB.release()
        mutexA.release()
    def func2(self):
        mutexB.acquire()
        print('\033[35m%s 拿到B锁\033[0m' % self.name)
        # time.sleep(2)
        mutexA.acquire()
        print('\033[30m%s 拿到A锁\033[0m' % self.name)
        mutexA.release()
        mutexB.release()
if __name__ == '__main__':
    for i in range(10):
        t = MyThread()
        t.start()
-------------------------------结果----------------------------

Thread-1 拿到A锁
Thread-1 拿到B锁
Thread-1 拿到B锁
Thread-1 拿到A锁
Thread-2 拿到A锁
Thread-2 拿到B锁
Thread-2 拿到B锁
Thread-2 拿到A锁
Thread-3 拿到A锁
Thread-3 拿到B锁
Thread-3 拿到B锁
Thread-3 拿到A锁
Thread-4 拿到A锁
Thread-4 拿到B锁
Thread-4 拿到B锁
Thread-4 拿到A锁
Thread-5 拿到A锁
Thread-5 拿到B锁
Thread-5 拿到B锁
Thread-5 拿到A锁
Thread-6 拿到A锁
Thread-6 拿到B锁
Thread-6 拿到B锁
Thread-6 拿到A锁
Thread-7 拿到A锁
Thread-7 拿到B锁
Thread-7 拿到B锁
Thread-7 拿到A锁
Thread-8 拿到A锁
Thread-8 拿到B锁
Thread-8 拿到B锁
Thread-8 拿到A锁
Thread-9 拿到A锁
Thread-9 拿到B锁
Thread-9 拿到B锁
Thread-9 拿到A锁
Thread-10 拿到A锁
Thread-10 拿到B锁
Thread-10 拿到B锁
Thread-10 拿到A锁

 

二十一:信号量(Semaphore)---------就是一把锁(可以定义钥匙)

同进程的一样

Semaphore管理一个内置的计数器,每当调用acquire()时内置计数器-1;调用release()时内置函数+1;计数器不能小于0,acquire()将阻塞线程直到其他线程调用release().

与进程池是完全不同的概念,进程池Pool(4),最大只能产生4个进程,而且从头到尾都只是这四个进程,不会产生新的,而信号量是产生一堆线程/进程。

实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):

#! usr/bin/env python
# -*- coding: utf-8 -*-
# from threading import Thread,Semaphore
# import threading
# import time
# def func():
#     sm.acquire()
#     print('%s get sm '%threading.current_thread().getName())
#     time.sleep(3)
#     sm.release()
# if __name__ == '__main__':
#     sm = Semaphore(5)#信号量
#     for i in range(23):
#         t=Thread(target=func)
#         t.start()
from threading import Thread,Semaphore,currentThread
import time,random
sm = Semaphore(5)
def talk():
    sm.acquire()
    print('%s 上厕所 '%currentThread().getName())
    time.sleep(random.randint(1,3))
    print('%s 完事 '%currentThread().getName())
    sm.release()
if __name__ == '__main__':
    for i in range(20):
        t = Thread(target=talk)
        t.start()
----------------------------结果---------------------------

Thread-1 上厕所
Thread-2 上厕所
Thread-3 上厕所
Thread-4 上厕所
Thread-5 上厕所
Thread-1 完事
Thread-6 上厕所
Thread-2 完事
Thread-7 上厕所
Thread-4 完事
Thread-8 上厕所
Thread-6 完事
Thread-9 上厕所
Thread-8 完事
Thread-10 上厕所
Thread-3 完事
Thread-11 上厕所
Thread-7 完事
Thread-5 完事
Thread-13 上厕所
Thread-12 上厕所
Thread-12 完事
Thread-14 上厕所
Thread-9 完事
Thread-15 上厕所
Thread-10 完事
Thread-16 上厕所
Thread-11 完事
Thread-17 上厕所
Thread-14 完事
Thread-18 上厕所
Thread-13 完事
Thread-19 上厕所
Thread-16 完事
Thread-20 上厕所
Thread-15 完事
Thread-18 完事
Thread-17 完事
Thread-20 完事
Thread-19 完事

 

 二十二:Event

同进程的一样

线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行

event.isSet():返回event的状态值;

event.wait():如果 event.isSet()==False将阻塞线程;

event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;

event.clear():恢复event的状态值为False。

python----进程与线程

from threading import Thread,Event,currentThread
import time
e = Event()
def traffic_lights():
    time.sleep(5)
    e.set()#设置
def car():
    print('\033[45m%s 等'%currentThread().getName())
    e.wait()      #等
    print('033[45m%s 跑'%currentThread().getName())
if __name__ == '__main__':
    for i in range(10):
        t = Thread(target=car)
        t.start()
    traffic_thread = Thread(target=traffic_lights)
    traffic_thread.start()

例如,有多个工作线程尝试链接MySQL,我们想要在链接前确保MySQL服务正常才让那些工作线程去连接MySQL服务器,如果连接不成功,都会去尝试重新连接。那么我们就可以采用threading.Event机制来协调各个工作线程的连接操作

#! usr/bin/env python
# -*- coding: utf-8 -*-
#练习一
# from threading import Thread,Event,currentThread
# import time
# e = Event()
# def traffic_lights():
#     time.sleep(5)
#     e.set()#设置
# def car():
#     print('\033[45m%s 等'%currentThread().getName())
#     e.wait()      #等
#     print('033[45m%s 跑'%currentThread().getName())
# if __name__ == '__main__':
#     for i in range(10):
#         t = Thread(target=car)
#         t.start()
#     traffic_thread = Thread(target=traffic_lights)
#     traffic_thread.start()
#练习二
from threading import Thread,Event
import threading
import time,random
def conn_mysql():
    count = 1
    while not event.is_set():#返回event的状态值
        if count > 3:
            raise TimeoutError('尝试链接次数过多')
        print('\033[45m<%s>第%s次尝试链接'%(threading.current_thread().getName(),count))
        event.wait(1)
        count+=1
    print('<%s>链接成功'%threading.current_thread().getName())
def check_mysq1():
    print('\033[45m[%s]正在检查mysql'%threading.current_thread().getName())
    time.sleep(random.randint(1,3))
    event.set()
if __name__ == '__main__':
    event = Event()
    conn1 = Thread(target=conn_mysql)
    conn2 = Thread(target=conn_mysql)
    check= Thread(target=check_mysq1)
    conn1.start()
    conn2.start()
    check.start()

二十三:条件Condition

使得线程等待,只有满足某条件时,才释放n个线程

import threading
 
def run(n):
    con.acquire()
    con.wait()
    print("run the thread: %s" %n)
    con.release()
 
if __name__ == '__main__':
 
    con = threading.Condition()
    for i in range(10):
        t = threading.Thread(target=run, args=(i,))
        t.start()
 
    while True:
        inp = input('>>>')
        if inp == 'q':
            break
        con.acquire()
        con.notify(int(inp))
        con.release()
def condition_func():

    ret = False
    inp = input('>>>')
    if inp == '1':
        ret = True

    return ret


def run(n):
    con.acquire()
    con.wait_for(condition_func)
    print("run the thread: %s" %n)
    con.release()

if __name__ == '__main__':

    con = threading.Condition()
    for i in range(10):
        t = threading.Thread(target=run, args=(i,))
        t.start()

二十四:定时器(Timer)

定时器--------------------------指定n秒后执行某操作

from threading import Timer
def hello():
    print('hello,悟空')
t = Timer(3,hello)
t.start()

二十五:线程queue(队列)

queue队列:使用import queue,用法与进程Queue一样

队列在线程编程中尤其有用,因为必须在多个线程之间安全地交换信息。

class queue.Queue(maxsize=0)-------先进先出
import queue

q=queue.Queue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())
'''
结果(先进先出):
first
second
third
'''

class queue.LifoQueue(maxsize=0)-------------先进后出(堆栈)

c
import queue

q=queue.LifoQueue()
q.put('first')
q.put('second')
q.put('third')

print(q.get())
print(q.get())
print(q.get())
'''
结果(后进先出):
third
second
first
'''

class queue.PriorityQueue(maxsize=0)-------------存储数据时可设置优先级的队列

import queue
q = queue.PriorityQueue()
#put进入一个元组,元组的第一个元素是优先级
# (通常是数字,也可以是非数字之间的比较),
#数字越小优先级越高
q.put((30,'a'))
q.put((20,'b'))
q.put((10,'c'))
print(q.get())
print(q.get())
print(q.get())
--------------------------------结果----------------------------
数字越小优先级越高

(10, 'c')
(20, 'b')
(30, 'a')

 

其他:
 
 
Constructor for a priority queue. maxsize is an integer that sets the upperbound limit on the number of items that can be placed in the queue. Insertion will block once this size has been reached, until queue items are consumed. If maxsize is less than or equal to zero, the queue size is infinite.

The lowest valued entries are retrieved first (the lowest valued entry is the one returned by sorted(list(entries))[0]). A typical pattern for entries is a tuple in the form: (priority_number, data).

exception queue.Empty
Exception raised when non-blocking get() (or get_nowait()) is called on a Queue object which is empty.

exception queue.Full
Exception raised when non-blocking put() (or put_nowait()) is called on a Queue object which is full.

Queue.qsize()
Queue.empty() #return True if empty  
Queue.full() # return True if full 
Queue.put(item, block=True, timeout=None)
Put item into the queue. If optional args block is true and timeout is None (the default), block if necessary until a free slot is available. If timeout is a positive number, it blocks at most timeout seconds and raises the Full exception if no free slot was available within that time. Otherwise (block is false), put an item on the queue if a free slot is immediately available, else raise the Full exception (timeout is ignored in that case).

Queue.put_nowait(item)
Equivalent to put(item, False).

Queue.get(block=True, timeout=None)
Remove and return an item from the queue. If optional args block is true and timeout is None (the default), block if necessary until an item is available. If timeout is a positive number, it blocks at most timeout seconds and raises the Empty exception if no item was available within that time. Otherwise (block is false), return an item if one is immediately available, else raise the Empty exception (timeout is ignored in that case).

Queue.get_nowait()
Equivalent to get(False).

Two methods are offered to support tracking whether enqueued tasks have been fully processed by daemon consumer threads.

Queue.task_done()
Indicate that a formerly enqueued task is complete. Used by queue consumer threads. For each get() used to fetch a task, a subsequent call to task_done() tells the queue that the processing on the task is complete.

If a join() is currently blocking, it will resume when all items have been processed (meaning that a task_done() call was received for every item that had been put() into the queue).

Raises a ValueError if called more times than there were items placed in the queue.

Queue.join() block直到queue被消费完毕
 
 

 -----------------------------------------------------------------------------------------------------------------------------------------------------

用于优先队列的构造函数。maxsize是一个整数,它设置了可以在队列中放置的项的数量上限。一旦达到这个大小,插入将阻塞,直到队列项被消耗。如果maxsize小于或等于0,队列大小是无限的。最低值项首先被检索(最低值项是按排序返回的值(列表(条目))0)。条目的典型模式是表单中的tuple:(prioritynumber、data)。异常队列。当非阻塞get()(或getnowait())在一个空的队列对象上调用时,会出现空的异常。异常队列。当非阻塞put()(或putnowait())在队列对象上被调用时,会出现完整的异常。Queue.qsize()队列。空()返回True,如果空队列。full()返回True,如果完全队列。put(项,块=True,超时=None)将项放入队列中。如果可选的args块是true,则超时为None(缺省值),如果需要则阻塞,直到空闲槽可用。如果超时是一个正数,它会在大多数超时秒中阻塞,如果在那个时间内没有空闲槽,则会引发完全的异常。否则(块是false),如果一个空闲槽立即可用,则在队列上放置一个项,否则就会抛出完全异常(在这种情况下会忽略超时)。队列。put(项)等价于put(项,False)。队列中。get(块=True,超时=None)从队列中删除并返回一个条目。如果可选的args块是true,则超时为None(缺省值),如果需要则阻塞,直到有可用的项。如果超时是一个正数,它会在大多数超时秒中阻塞,如果在那个时间内没有可用的项,则会抛出空的异常。否则(块是false),如果立即可用,返回一个项目,否则将抛出空异常(在这种情况下忽略超时)。Queue.get_nowait()相当于获得(假)。提供了两种方法来支持跟踪队列的任务是否已经被守护进程的消费者线程完全地处理。queue.task完成()表明以前的队列任务已经完成。用于队列消费线程。对于用于获取任务的每个get(),随后对taskcomplete()的调用告诉队列,任务的处理是完成的。如果一个join()当前阻塞,那么当所有项都被处理时,它将恢复(这意味着一个task做完()调用将被接收到的每个条目()到队列中)。如果调用的次数多于队列中放置的项,则会产生一个ValueError。Queue.join()块直到队列被消费完毕