线程

简介

一.操作系统线程理论

  1.线程概念的引入背景

  2.线程的特点

  3.进程和线程的关系

  4.使用线程的实际场景

  5.用户级线程和内核级线程

二.线程和python

  1.理论知识

  2.线程的创建 Threading.Thread 类

  3.锁

  4.队列

  5.python标准模块---- concurrent.futures

  操作系统线程理论

  1.线程概念的引入背景:

 进程:

  之前了解了操作系统中进程的概念,程序并不能单独运行,只有将程序装到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程. 程序和进程的区别在于:程序是指令的集合,它是进程运行的静态描述文本,进程是 程序的一次执行活动,属于动态概念. 在多道编程中,我们允许多个进程同时加载到内存中,在操作系统的调度下,可以实现并发的执行,这样的设计,大大提高了 CPU 的利用率,进程的出现让每个用户感觉到自己独享 CPU,因此,进程就是为了在 CPU 上实现多道编程而提出的.

有了进程为什么要有线程:

  进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU 和其他资源,可以提高计算机的利用率,那就不能理解了,既然 进程这么优秀,为什么还需要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:

  1.进程只能在一个时间干一件事如果想同时干两件事或多件事,进程就无能为力了.

  2.进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法还行.

  这两个缺点理解起来有点困难??? 举个实现的例子:如果把我们上课的过程看成一个进程的话,那么我们要做的是耳朵听老师讲课,手上还要做笔记,脑子还要思考问题,这样才能高效的完成听课的任务.而如果只提供进程这个机制的话,上面的三件事将不能同时执行,同一时间只能做一件事,听的时候就不能做笔记,也不能用脑子思考,这是其一,如果老师在黑板上写演算过程,我们开始做笔记,而老师突然有一步推不下去了,阻塞了,他在那边思考着,而我们呢?? 也不能干其他的事,即使你想趁现在思考下刚才没听懂的一个问题都不行,这是其二.

  现在就可以完全理解了进程的缺陷,而解决的办法很简单,我们完全可以让听,写,思考 三个独立的过程,并行起来,这样很明显可以提高听课的效率,而实际的操作系统中,也同样引入了这样类似的机制-----线程

线程的出现:

    60年代,在os 中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了很多弊端, 一:由于进程是资源的拥有者,创建,撤销 与 切换 存在较大的时空开销,因此需要引入 轻型进程. 二:由于对称多处理机(SMP) 出现,可以满足多个运行单位,而多个进程并行开销过大.

  对称多处理机,在均匀存储器访问(UMA)系统中,一个共享存储器可以为所有处理器通过一个互联网络进行访问,就如同一个单处理器访问它的存储器一样。所有处理器对任何存储单元有相同的访问时间。用于UMA中的互联网络可以是单总线、多总线或者是交叉开关。因为对共享存储器的访问是平衡的,故这类系统称为SMP(对称多处理器)系统。每个处理器有相等的机会读/写存储器,也有相同的访问速度。

  因此,在80年代,出现了 能独立运行的基本单位-----线程(Threads).

  注意:进程是资源分配的最小单位,线程是 CPU 调度的最小单位.

      每一个进程最少有一个线程.

进程和线程的关系:

  

  进程和线程的区别在于以下 4点:

  1.地址空间和其他资源,(如打开文件):进程间相互独立,同一进程的各线程间共享,某进程内的线程在其他进程不可见.

  2.通信: 进程间通信 IPC,线程间可以直接读写进程数据段(如全局变量) 来进行通信 --------需要进程同步 和 互斥 手段的辅助,以保证数据的一致性.

  3.调度 和 切换:线程上下文切换比进程上下文切换要快的多.

  4.在多线程操作系统中,进程不是一个可执行的实体.

  工厂模型:

 

  线程的特点:

  在多线程的操作系统中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体,线程具有以下属性:

  1.轻型实体

  线程中的实体基本上不拥有系统资源,只是有一点必不可少的,能保证独立运行的资源

  线程的实体包括程序,数据和TCB. 线程是动态概念,它的动态特性由线程控制块TCB(Thread  Control Block) 描述.

TCB包括以下信息:
1.线程状态
2.当线程不运行时,被保存的现场资源
3.一组执行堆栈
4.存放每个线程的局部变量主存区
5.访问同一个进程中的主存和其他资源

用于指示被执行指令序列的程序计数器,保留局部变量,少数状态参数和返回地址等的一组寄存器和堆栈
TCB包括以下信息

  2.独立调度和分派的基本单位

  在多线程 os中,线程是能独立运行的基本单位,因而也是独立调度和分配的基本单位,由于线程很轻,故线程的切换非常迅速且开销小(在同一进城中的)

  3.共享进程资源

  线程在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的进程 id,这意味着,线程可以访问该进程的每一个内存资源,此外,还可以访问进程所拥有的已打开文件 ,定时器,信号量机构等.由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核

  4.可并发执行

  在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行,同样.不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作 的能力

使用线程的实际场景

   

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

内存中的线程

  

  多个线程共享同一个进程的地址空间中的资源,是对一台计算机上多个进程的模拟,有时也称线程为轻量级的进程

  而对一台计算机上的多个进程,则共享物理内存,磁盘,打印机等其他物理资源,多线程的运行与多进程的运行类似,是CPU在多个线程之间的快速切换

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

  类似于进程,每个线程有自己的堆栈,不同于进程,线程库无法利用时钟中断强制线程让出CPU,可以调用 thread_yield 运行线程自动放弃 cup,让另外一个线程运行.

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

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

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

  因此,在多线程的代码中,需要更多的心思来设计程序的逻辑,保护程序的数据

  用户级线程和内核级线程

  线程的实现可以分为两类: 

  1.用户级线程:(User_level Thread )

  2.内核级线程:(Kernel_level Thread ) 内核支持的线程 或 轻量级进程 

  在多线程操作系统中,各个系统的实现方式并不相同,在有的系统中实现了用户级线程,有的系统中实现了内核级线程

  用户级线程:

     内核的切换由用户态程序自己控制内核切换,不需要内核干涉,少了进出内核态的消耗,但不能很好的利用 CPU

  

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

内核级线程

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

  

用户级 与 内核级 线程的对比

  

1.内核支持线程是OS内核可感知的,而用户线程是OS内核不可感知的
2.用户级线程的创建,撤销,调度不需要 OS内核的支持,是在语言(入Java)这一级处理的,而内核支持线程的创建,撤销,调度都是OS内核提供支持,而且与进程的创建,撤销,调度大体是相同的
3.用户级线程执行系统调用指令时将导致所属进程被中断,而内核支持线程执行系统调用指令时,只导致该线程被中断
4.在只有用户级线程的系统中,CPU调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行,在有内核支持线程的系统中,CPU调度则以线程为单位,由 OS 的线程调度程序负责线程的调度
5.用户级线程的程序实体是运行在用户态下的程序,而内核支持线程的程序实体则是可以运行在任何状态下的程序
用户级线程 和 内核级线程的区别
优点: 当有多个处理机时,一个进程的多个线程可以同时执行
缺点: 又内核进行调度
内核线程的优缺点
优点:
1.线程的调度不需要内核直接参与,控制简单
2.可以在不支持线程的操作系统中实现
3.创建和销毁线程,线程切换代价等线程管理的代价比内核线程少的多
4.允许每个进程定制自己的调度算法,线程管理比较灵活
5.线程能够利用的表空间和堆栈空间比内核级线程多
6.同一进程中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起,另外,页面失效也会产生同样的问题


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

混合实现:

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

            

Linux 操作系统的 NPTL

历史
在内核2.6以前的调度实体都是进程,内核并没有真正支持线程。它是能过一个系统调用clone()来实现的,这个调用创建了一份调用进程的拷贝,跟fork()不同的是,这份进程拷贝完全共享了调用进程的地址空间。LinuxThread就是通过这个系统调用来提供线程在内核级的支持的(许多以前的线程实现都完全是在用户态,内核根本不知道线程的存在)。非常不幸的是,这种方法有相当多的地方没有遵循POSIX标准,特别是在信号处理,调度,进程间通信原语等方面。

很显然,为了改进LinuxThread必须得到内核的支持,并且需要重写线程库。为了实现这个需求,开始有两个相互竞争的项目:IBM启动的NGTP(Next Generation POSIX Threads)项目,以及Redhat公司的NPTL。在2003年的年中,IBM放弃了NGTP,也就是大约那时,Redhat发布了最初的NPTL。

NPTL最开始在redhat linux 9里发布,现在从RHEL3起内核2.6起都支持NPTL,并且完全成了GNU C库的一部分。

 

设计
NPTL使用了跟LinuxThread相同的办法,在内核里面线程仍然被当作是一个进程,并且仍然使用了clone()系统调用(在NPTL库里调用)。但是,NPTL需要内核级的特殊支持来实现,比如需要挂起然后再唤醒线程的线程同步原语futex.

NPTL也是一个1*1的线程库,就是说,当你使用pthread_create()调用创建一个线程后,在内核里就相应创建了一个调度实体,在linux里就是一个新进程,这个方法最大可能的简化了线程的实现。

除NPTL的1*1模型外还有一个m*n模型,通常这种模型的用户线程数会比内核的调度实体多。在这种实现里,线程库本身必须去处理可能存在的调度,这样在线程库内部的上下文切换通常都会相当的快,因为它避免了系统调用转到内核态。然而这种模型增加了线程实现的复杂性,并可能出现诸如优先级反转的问题,此外,用户态的调度如何跟内核态的调度进行协调也是很难让人满意。
介绍

  线程 和 python

 理论知识:

全局解释器锁Gil

  python 代码的执行由 python 虚拟机(也叫解释器循环) 来控制,python 在设计之初就考虑到要在主循环中,同时只有一个线程在执行,虽然 python 解释器中可以 "运行" 多个线程,但在任意时刻只有一个线程在解释器中运行.

  对python 虚拟机 的访问由 全局解释器锁(Gil) 来控制,正是这个锁保证同一时刻只有一个线程在运行

  在多线程环境中,python 虚拟机按照以下方式执行:

  1.设置Gil

  2.切换到一个线程去执行

  3.运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep())

  4.把线程设置为睡眠状态

  5.解锁 Gil

  6.再次重复以上所有步骤

  在调用外部代码 (如 C/C++扩展函数) 的时候,Gil 将会被锁定,直到这个函数结束为止( 由于在这期间没有python的字节码被运行,所以不会做线程切换) 编写扩展的程序员可以主动解锁Gil

python线程模块的选择

  python 提供了几个用于多线程编程的模块,包括 thread,threading,和 Queue 等,thread 和 threading 模块允许程序员创建和管理线程,thread模块提供了基本的线程和锁的支持,threading提供了更高级别,功能更强的线程管理的功能,Queue 模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构.

  避免使用thread模块,因为更高级别的 threading 模块更为先进,对线程的支持更为完善,而且使用 thread 模块里的属性有可能会与 threading 出现冲突,其次低级别的 thread模块的同步原语很少(实际上只有一个),而threading模块则很多,再者,thread 模块中当主线程结束时,所有的线程都会被强制结束,没有警告也不会有正常的清除工作,至少 threading 模块能确保重要的子线程退出后进程才退出.

  thread 模块不支持守护线程,当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出,而threading 模块支持守护进程,守护进程一般是一个等待客户请求的服务器,如果没有客户提出请求它就在那等着,如果设定一个守护进程,就表示这个县城是不重要的,在进程退出的时候,不用等这个线程退出

thread模块

  multiprosess 模块的完全模仿了 threading 模块的接口,二者在使用层面,有很大的相似性,因而不再详细介绍

  threading -- 基于线程的并行性

threading-- 基于线程的并行性
该模块在较低级别模块之上构建更高级别的线程接口_thread,queue模块
在版本3.7中更改:此模块曾经是可选的,现在始终可用
注意:
        虽然他们未在下面列出,但 camelCase 此模块仍支持 python2.x 系列中此模块中某些 方法和函数的名称

该模块定义了以下的功能:

1.threading.active_count()
    返回 Thread 当前活动的对象数,返回的计数等于返回的列表的长度        
                enumerate().
2.threading.current_thread()
      返回当前 thread 对象,对应于调用者的控制线程,如果未通过模块创建调用 
               者的控制线程,则返回具有有限功能的虚拟线程对象.threading
3.threading.get_ident()
    返回当前线程的"线程标示符",这是一个非0整数,他的价值没有直接意义,它旨在用作例如索引线程待定的字典的魔术cookie,当线程退出并创建另一个线程时,线程标识符可以被收回
    版本3.3的新功能

4.threading.enumerate()
    返回 thread 当前活动的所有对象的列表,该列表包括守护进程,由其创建的虚拟线程对象
    current_thread() 和 主线程,它排除了尚未启动的已终止线程和线程

5.threading.main_thread()
    返回主 thread 对象,在正常情况下,主线程是启动 python 解释器的线程
    版本3.4 的新功能

6.threading.settrace(func)
      为从模块启动的所有线程设置跟踪功能,在调用其方法之前,将为每个线程传递 func .
      threadingsys.settrace()run()

7.threading.setprofile(func)
       为从模块启动的所有线程设置配置文件功能,在调用其方法之前,将为每个线程传递 func 
     threadingsys.setprofile()run()

8.threading.stack_size([大小])
      返回创建新线程时使用的线程堆栈大小。可选的 size参数指定用于后续创建的线程的堆栈大小,并且必须为0(使用平台或配置的默认值)或至少为32,768(32 KiB)的正整数值。如果未指定大小,则使用0。如果不支持更改线程堆栈大小,RuntimeError则引发a。如果指定的堆栈大小无效,则aValueError被提升,堆栈大小未经修改。32 KiB是目前支持的最小堆栈大小值,以保证解释器本身有足够的堆栈空间。请注意,某些平台可能对堆栈大小的值有特定限制,例如要求最小堆栈大小> 32 KiB或需要以系统内存页面大小的倍数进行分配 - 应提供平台文档以获取更多信息(4 KiB页面)很常见;在没有更具体信息的情况下,建议的方法是使用4096的倍数作为堆栈大小。

可用性:Windows,具有POSIX线程的系统。

9.threading.TIMEOUT_MAX
允许的最大值的超时阻断功能的(参数Lock.acquire(),RLock.acquire(),Condition.wait()等等)。指定超过此值的超时将引发一个 OverflowError。

版本3.2中的新功能。

该模块定义了许多类,详见以下部分。

该模块的设计基于Java的线程模型。但是,在Java使锁和条件变量成为每个对象的基本行为的地方,它们是Python中的独立对象。Python的Thread类支持Java的Thread类的行为的子集; 目前,没有优先级,没有线程组,线程不能被销毁,停止,暂停,恢复或中断。Java的Thread类的静态方法在实现时会映射到模块级函数。

下面描述的所有方法都是原子执行的。

线程局部数据
线程局部数据是其值是线程特定的数据。要管理线程本地数据,只需创建一个local(或子类)实例并在其上存储属性:

mydata = threading.local()
mydata.x = 1
对于单独的线程,实例的值将不同。

类threading.local
表示线程本地数据的类。

有关更多详细信息和大量示例,请参阅模块的文档字符串 。_threading_local

线程对象
的Thread类表示在单独的控制线程中运行的活动。有两种方法可以指定活动:将可调用对象传递给构造函数,或者通过覆盖run() 子类中的方法。不应在子类中重写其他方法(构造函数除外)。换句话说,只 覆盖此类的 __init__()和run()方法。

创建线程对象后,必须通过调用线程的start()方法启动其活动。这将run() 在单独的控制线程中调用该方法。

一旦线程的活动开始,线程就被认为是“活着的”。当它的run()方法终止时,它会停止活动- 通常,或者通过引发未处理的异常。该is_alive() 方法测试线程是否存活。

其他线程可以调用线程的join()方法。这会阻塞调用线程,直到调用其join()方法的线程终止。

线程有一个名字。名称可以传递给构造函数,并通过name属性读取或更改。

线程可以标记为“守护程序线程”。这个标志的意义在于,当只剩下守护进程线程时,整个Python程序都会退出。初始值继承自创建线程。可以通过daemon属性或守护进程构造函数参数设置该标志。

注意 守护程序线程在关闭时突然停止。他们的资源(例如打开文件,数据库事务等)可能无法正确发布。如果您希望线程正常停止,请使它们成为非守护进程并使用合适的信令机制,例如Event。
有一个“主线程”对象; 这对应于Python程序中的初始控制线程。它不是守护程序线程。

有可能创建“虚拟线程对象”。这些是对应于“外部线程”的线程对象,它们是在线程模块外部启动的控制线程,例如直接来自C代码。虚拟线程对象具有有限的功能; 他们总是被认为是活着的和守护的,不能被join()编辑。它们永远不会被删除,因为无法检测外来线程的终止。

class (group = None,target = None,name = None,args =(),kwargs = {},*,daemon = None )threading.Thread
应始终使用关键字参数调用此构造函数。参数是:

小组应该None; 在实现ThreadGroup类时为将来的扩展保留 。

target是run()方法调用的可调用对象。默认为None,意味着什么都没有被调用。

name是线程名称。默认情况下,唯一名称由“Thread- N ” 形式构成,其中N是小十进制数。

args是目标调用的参数元组。默认为()。

kwargs是目标调用的关键字参数字典。默认为{}。

如果没有None,守护进程显式设置线程是否为守护进程。如果None(默认值),则从当前线程继承守护进程属性。

如果子类重写构造函数,则必须确保Thread.__init__()在对线程执行任何其他操作之前调用基类构造函数()。

版本3.3中已更改:添加了守护程序参数。

start()
启动线程的活动。

每个线程对象最多只能调用一次。它安排run()在单独的控制线程中调用对象的方法。

此方法将RuntimeError在同一个线程对象上多次调用if。

run()
表示线程活动的方法。

您可以在子类中覆盖此方法。标准run() 方法调用传递给对象构造函数的可调用对象作为目标参数(如果有),分别使用args和kwargs参数中的顺序和关键字参数。

join(超时=无)
等到线程终止。这将阻塞调用线程,直到调用其join()方法的线程终止 - 正常或通过未处理的异常 - 或直到发生可选的超时。

当超时参数存在而不存在时None,它应该是一个浮点数,指定操作的超时(以秒为单位)(或其分数)。由于join()总是返回None,必须调用is_alive()后join()决定超时是否发生了-如果线程还活着时, join()调用超时。

当timeout参数不存在时None,操作将阻塞,直到线程终止。

线程可以join()多次编辑。

join()提出了RuntimeError如果试图加入当前线程因为这将导致死锁。join()在线程启动之前它也是一个错误,并且尝试这样做会引发相同的异常。

name
字符串仅用于识别目的。它没有语义。多个线程可以赋予相同的名称。初始名称由构造函数设置。

getName()
setName()
旧的getter / setter API用于name; 直接将其用作属性。

ident
此线程的“线程标识符”或者None线程尚未启动。这是一个非零整数。看get_ident() 功能。当线程退出并创建另一个线程时,线程标识符可以被回收。即使在线程退出后,该标识符也可用。

is_alive()
返回线程是否存活。

此方法True在run()方法启动之前返回,直到run()方法终止之后。模块函数enumerate()返回所有活动线程的列表。

daemon
一个布尔值,指示此线程是否为守护程序线程(True)或不是(False)。必须在start()调用之前设置,否则RuntimeError引发。它的初始值继承自创建线程; 主线程不是守护程序线程,因此在主线程中创建的所有线程都默认为 daemon= False。

当没有剩下活着的非守护程序线程时,整个Python程序退出。

isDaemon()
setDaemon()
旧的getter / setter API用于daemon; 直接将其用作属性。

CPython实现细节:在CPython中,由于Global Interpreter Lock,只有一个线程可以同时执行Python代码(即使某些面向性能的库可能会克服此限制)。如果您希望应用程序更好地利用多核计算机的计算资源,建议您使用 multiprocessing或concurrent.futures.ProcessPoolExecutor。但是,如果要同时运行多个I / O绑定任务,则线程仍然是一个合适的模型。

锁定对象
原始锁是一种同步原语,在锁定时不属于特定线程。在Python中,它是目前可用的最低级别同步原语,由_thread 扩展模块直接实现。

原始锁定处于“锁定”或“解锁”两种状态之一。它是在解锁状态下创建的。它有两种基本方法,acquire()和 release()。当状态解锁时,acquire() 将状态更改为已锁定并立即返回。当状态被锁定时, acquire()阻塞直到release()另一个线程中的调用将其更改为解锁,然后该acquire()调用将其重置为已锁定并返回。该release()方法只应在锁定状态下调用; 它将状态更改为已解锁并立即返回。如果尝试释放未锁定的锁, RuntimeError则会引发a。

锁还支持上下文管理协议。

当多个线程在acquire()等待状态转为解锁时被阻塞时,只有一个线程在release() 呼叫重置状态解锁时继续; 哪个等待线程继续进行的定义没有定义,并且可能因实现而异。

所有方法都以原子方式执行。

类threading.Lock
实现原始锁定对象的类。一旦线程获得了一个锁,后续尝试获取它就会阻塞,直到它被释放; 任何线程都可以释放它。

请注意,Lock它实际上是一个工厂函数,它返回平台支持的具体Lock类的最高效版本的实例。

acquire(blocking = True,timeout = -1 )
获取锁定,阻止或非阻止。

当阻塞参数设置为True(默认值)时调用,阻塞直到解锁,然后将其设置为锁定并返回True。

在阻塞参数设置为的情况下调用时False,请勿阻止。如果阻塞设置为True阻塞,则False 立即返回; 否则,将锁定设置为锁定并返回True。

在浮点超时参数设置为正值的情况下调用时,最多阻止超时指定的秒数, 并且只要无法获取锁定。一个超时的参数-1 指定了一个无限的等待。阻止为false 时禁止指定超时。

返回值True是否成功获取锁定, False否则(例如超时到期)。

改变在3.2版本:该超时参数是新的。

在3.2版中更改:如果底层线程实现支持,则可以通过POSIX上的信号中断锁获取。

release()
解锁。这可以从任何线程调用,而不仅仅是获取锁的线程。

锁定锁定后,将其重置为解锁状态,然后返回。如果阻止任何其他线程等待锁解锁,则只允许其中一个继续执行。

在未锁定的锁上调用时,RuntimeError会引发a。

没有回报价值。

RLock对象
可重入锁是同步原语,可以由同一线程多次获取。在内部,除了原始锁使用的锁定/解锁状态之外,它还使用“拥有线程”和“递归级别”的概念。在锁定状态下,某些线程拥有锁; 在解锁状态下,没有线程拥有它。

要锁定锁,线程会调用其acquire()方法; 一旦线程拥有锁,它就会返回。要解锁锁,线程会调用其release()方法。acquire()/ release() call对可以嵌套; 只有最后一个release()( release()最外面的一对)重置锁才能解锁并允许另一个被阻塞的线程acquire()继续进行。

可重入锁也支持上下文管理协议。

类threading.RLock
此类实现可重入锁定对象。必须由获取它的线程释放可重入锁。一旦线程获得了可重入锁,同一个线程可以再次获取它而不会阻塞; 线程必须在每次获取它时释放一次。

请注意,RLock它实际上是一个工厂函数,它返回平台支持的具体RLock类的最高效版本的实例。

acquire(blocking = True,timeout = -1 )
获取锁定,阻止或非阻止。

在不带参数的情况下调用:如果此线程已拥有锁,则将递归级别递增1,并立即返回。否则,如果另一个线程拥有该锁,则阻塞直到锁被解锁。锁解锁后(不属于任何线程),然后获取所有权,将递归级别设置为1,然后返回。如果多个线程被阻塞等待锁解锁,则一次只能有一个线程获取锁的所有权。在这种情况下没有返回值。

在将blocking参数设置为true的情况下调用时,执行与不带参数调用时相同的操作,并返回true。

在将blocking参数设置为false的情况下调用时,请勿阻止。如果没有参数的调用会阻塞,则立即返回false; 否则,执行与不带参数调用时相同的操作,并返回true。

在浮点超时参数设置为正值的情况下调用时,最多阻止超时指定的秒数, 并且只要无法获取锁定。如果已获取锁定则返回true,如果超时已过,则返回false。

改变在3.2版本:该超时参数是新的。

release()
释放锁定,递减递归级别。如果在递减之后它为零,则将锁重置为未锁定(不由任何线程拥有),并且如果阻止任何其他线程等待锁解锁,则允许其中一个继续进行。如果在递减之后递归级别仍然非零,则锁保持锁定并由调用线程拥有。

仅在调用线程拥有锁时调用此方法。RuntimeError如果在锁定解锁时调用此方法,则引发A.

没有回报价值。

条件对象
条件变量总是与某种锁相关联; 这可以传入,或者默认情况下会创建一个。当多个条件变量必须共享同一个锁时,传入一个很有用。锁是条件对象的一部分:您不必单独跟踪它。

条件变量服从上下文管理协议:使用该with语句在封闭块的持续时间内获取关联的锁。该acquire()和 release()方法也调用相关的锁的相应方法。

必须在保持关联锁的情况下调用其他方法。该 wait()方法释放锁,然后阻塞,直到另一个线程通过调用notify()或 唤醒它notify_all()。一旦被唤醒,wait() 重新获得锁并返回。也可以指定超时。

该notify()方法唤醒等待条件变量的其中一个线程(如果有的话)正在等待。该notify_all() 方法唤醒等待条件变量的所有线程。

注意:notify()和notify_all()方法不释放锁; 这意味着被唤醒的一个或多个线程不会wait()立即从它们的调用中返回,而是仅在调用notify()或notify_all() 最终放弃锁的所有权的线程中。

使用条件变量的典型编程风格使用锁来同步对某些共享状态的访问; 对状态的特定变化感兴趣的线程wait()重复调用,直到它们看到所需的状态,而线程修改状态调用 notify()或者notify_all()当它们改变状态时,它可能是其中一个服务员的期望状态。例如,以下代码是具有无限缓冲区容量的通用生产者 - 消费者情况:

# Consume one item
with cv:
    while not an_item_is_available():
        cv.wait()
    get_an_available_item()

# Produce one item
with cv:
    make_an_item_available()
    cv.notify()
该while循环检查应用程序的条件是必要的,因为wait()可以任意长的时间后返回,并促使病情notify()通话可能不再成立。这是多线程编程所固有的。该 wait_for()方法可用于自动化条件检查,并简化超时计算:

# Consume an item
with cv:
    cv.wait_for(an_item_is_available)
    get_an_available_item()
要在notify()和之间进行选择notify_all(),请考虑一个状态更改是否只对一个或多个等待线程感兴趣。例如,在典型的生产者 - 消费者情况下,向缓冲区添加一个项目只需要唤醒一个消费者线程。

class (lock = None )threading.Condition
此类实现条件变量对象。条件变量允许一个或多个线程等待,直到另一个线程通知它们。

如果给定了lock参数None,则它必须是一个Lock 或RLock对象,并且它被用作底层锁。否则,将RLock创建一个新对象并将其用作基础锁。

在版本3.3中更改:从工厂功能更改为类。

acquire(* args )
获取底层锁。此方法在底层锁上调用相应的方法; 返回值是该方法返回的任何值。

release()
释放底层锁。此方法在底层锁上调用相应的方法; 没有回报价值。

wait(超时=无)
等到通知或直到发生超时。如果在调用此方法时调用线程尚未获取锁定,RuntimeError则引发a。

此方法释放底层锁,然后阻塞,直到它被另一个线程中的相同条件变量唤醒notify()或notify_all()调用,或者直到发生可选超时。一旦被唤醒或超时,它就会重新获得锁定并返回。

当超时参数存在而不存在时None,它应该是一个浮点数,指定操作的超时(以秒为单位)(或其分数)。

当底层锁是a时RLock,它不会使用其release()方法释放,因为当递归多次获取时,这实际上可能无法解锁。相反,使用了RLock类的内部接口,即使多次递归获取它也能真正解锁它。然后,在重新获取锁时,使用另一个内部接口来恢复递归级别。

返回值是True除非给定的超时到期,在这种情况下它是False。

版本3.2中已更改:以前,该方法始终返回None。

wait_for(谓词,超时=无)
等到条件评估为真。 谓词应该是可调用的,结果将被解释为布尔值。一个超时可以提供给最长等待时间。

此实用程序方法可以wait()重复调用,直到满足谓词,或者直到超时发生。返回值是谓词的最后一个返回值,并将评估 False方法是否超时。

忽略超时功能,调用此方法大致相当于编写:

while not predicate():
    cv.wait()
因此,相同的规则适用于wait():锁定必须在被调用时保持并在返回时重新获取。使用锁定评估谓词。

版本3.2中的新功能。

notify(n = 1 )
默认情况下,唤醒一个等待此条件的线程(如果有)。如果在调用此方法时调用线程尚未获取锁定, RuntimeError则引发a。

此方法最多唤醒等待条件变量的n个线程; 如果没有线程在等待,那么这是一个无操作。

如果至少有n个 线程在等待,那么当前的实现正好会唤醒n 个线程。但是,依赖这种行为是不安全的。未来的优化实现有时可能会唤醒超过 n个线程。

注意:唤醒线程实际上不会从其wait() 调用返回,直到它可以重新获取锁定。由于notify()不释放锁,其调用者应该。

notify_all()
唤醒等待这种情况的所有线程。此方法就像 notify(),但唤醒所有等待的线程而不是一个。如果在调用此方法时调用线程尚未获取锁定, RuntimeError则引发a。

信号量对象
这是计算机科学史上最古老的同步原语之一,由早期荷兰计算机科学家Edsger W. Dijkstra(他使用名称P()而V()不是acquire()和 release())发明。

信号量管理内部计数器,该计数器按每次acquire()调用递减并按每次 调用递增release() 。计数器永远不会低于零; 当acquire() 发现它为零时,它会阻塞,等待其他线程调用 release()。

信号量还支持上下文管理协议。

class (value = 1 )threading.Semaphore
该类实现信号量对象。信号量管理一个原子计数器,表示release()调用次数减去acquire()调用次数 加上初始值。该acquire()方法在必要时阻止,直到它可以返回而不使计数器为负。如果没有给出,则值默认为1。

可选参数给出内部计数器的初始值 ; 它默认为1。如果给定的值小于0,ValueError则引发。

在版本3.3中更改:从工厂功能更改为类。

acquire(blocking = True,timeout = None )
获取信号量。

在不带参数的情况下调用:

如果内部计数器在进入时大于零,则将其递减1并立即返回true。
如果内部计数器在进入时为零,则阻止直到通过调用唤醒 release()。一旦唤醒(并且计数器大于0),将计数器递减1并返回true。每次调用都会唤醒一个线程release()。不应依赖线程被唤醒的顺序。
当阻塞设置为false 时调用,请勿阻止。如果没有参数的调用会阻塞,则立即返回false; 否则,执行与不带参数调用时相同的操作,并返回true。

当使用超时之外的超时调用时None,它将阻止最多超时秒。如果在该时间间隔内未成功完成获取,则返回false。否则返回true。

改变在3.2版本:该超时参数是新的。

release()
释放信号量,将内部计数器递增1。当它在进入时为零并且另一个线程正在等待它再次大于零时,唤醒该线程。

class (value = 1 )threading.BoundedSemaphore
实现有界信号量对象的类。有界信号量检查以确保其当前值不超过其初始值。如果确实如此, ValueError则提出。在大多数情况下,信号量用于保护容量有限的资源。如果信号量被释放太多次,则表明存在错误。如果没有给出,则值默认为1。

在版本3.3中更改:从工厂功能更改为类。

Semaphore示例
信号量通常用于保护容量有限的资源,例如数据库服务器。在资源大小固定的任何情况下,您应该使用有界信号量。在生成任何工作线程之前,您的主线程将初始化信号量:

maxconnections = 5
# ...
pool_sema = BoundedSemaphore(value=maxconnections)
一旦产生,工作线程在需要连接到服务器时调用信号量的获取和释放方法:

with pool_sema:
    conn = connectdb()
    try:
        # ... use connection ...
    finally:
        conn.close()
使用有界信号量可以减少导致信号量被释放的编程错误超过其获取的编程错误的可能性。

事件对象
这是线程之间通信的最简单机制之一:一个线程发出事件信号,其他线程等待它。

事件对象管理一个内部标志,该标志可以使用该set()方法设置为true,并使用该 方法重置为false clear() 。该wait()方法将阻塞,直到该标志为真。

类threading.Event
实现事件对象的类。事件管理一个标志,该标志可以使用该set()方法设置为true,并使用该方法重置为false clear()。该wait()方法将阻塞,直到该标志为真。该标志最初是假的。

在版本3.3中更改:从工厂功能更改为类。

is_set()
当且仅当内部标志为真时返回true。

set()
将内部标志设置为true。等待它变为真的所有线程都被唤醒。wait()一旦标志为真,调用的线程将不会阻塞。

clear()
将内部标志重置为false。随后,线程调用 wait()将阻塞,直到set()被调用以再次将内部标志设置为true。

wait(超时=无)
阻止,直到内部标志为真。如果输入时内部标志为真,则立即返回。否则,阻塞直到另一个线程调用 set()将标志设置为true,或者直到发生可选的超时。

当超时参数存在而不存在时None,它应该是一个浮点数,指定操作的超时(以秒为单位)(或其分数)。

当且仅当内部标志在等待调用之前或等待开始之后设置为true时,此方法返回true,因此True除非给出超时且操作超时,否则它将始终返回。

在3.1版中更改:以前,该方法始终返回None。

定时器对象
此类表示仅在经过一定时间后才应运行的操作 - 计时器。 Timer是一个子类,Thread 因此也可以作为创建自定义线程的示例。

通过调用start() 方法,计时器与线程一样启动。通过调用cancel()方法可以停止计时器(在其动作开始之前) 。计时器在执行其操作之前等待的时间间隔可能与用户指定的时间间隔不完全相同。

例如:

def hello():
    print("hello, world")

t = Timer(30.0, hello)
t.start()  # after 30 seconds, "hello, world" will be printed
class (interval,function,args = None,kwargs = None )threading.Timer
创建一个定时器,在经过间隔秒后,将使用参数args和关键字参数kwargs运行函数。如果args是(默认值),则将使用空列表。如果kwargs是(默认值),那么将使用空的dict。NoneNone

在版本3.3中更改:从工厂功能更改为类。

cancel()
停止计时器,取消执行计时器的操作。这仅在定时器仍处于等待阶段时才有效。

屏障对象
版本3.2中的新功能。

此类提供了一个简单的同步原语,供需要相互等待的固定数量的线程使用。每个线程都试图通过调用wait()方法来传递屏障,并将阻塞直到所有线程都进行了wait()调用。此时,线程同时释放。

对于相同数量的线程,屏障可以重复使用任意次数。

例如,这是一种同步客户端和服务器线程的简单方法:

b = Barrier(2, timeout=5)

def server():
    start_server()
    b.wait()
    while True:
        connection = accept_connection()
        process_server_connection(connection)

def client():
    b.wait()
    while True:
        connection = make_connection()
        process_client_connection(connection)
class (派对,动作=无,超时=无)threading.Barrier
为派对数量的线程创建一个屏障对象。提供的动作是一个可调用的,当它们被释放时由其中一个线程调用。 如果没有为方法指定none,则timeout是默认超时值wait()。

wait(超时=无)
通过障碍。当屏障的所有线程都调用此函数时,它们都会同时释放。如果提供了超时,则优先使用它来提供给类构造函数的任何超时。

返回值是0到party - 1 范围内的整数,每个线程都不同。这可以用来选择一个线程来做一些特殊的内务管理,例如:

i = barrier.wait()
if i == 0:
    # Only one thread needs to print this
    print("passed the barrier")
如果向构造函数提供了一个操作,则其中一个线程将在释放之前调用它。如果此调用引发错误,则屏障将进入损坏状态。

如果呼叫超时,屏障将进入断开状态。

BrokenBarrierError如果在线程等待时屏障被破坏或重置,则此方法可能引发异常。

reset()
将屏障恢复为默认的空状态。等待它的任何线程都将收到BrokenBarrierError异常。

请注意,如果存在状态未知的其他线程,则使用此函数可能需要一些外部同步。如果障碍被打破,最好离开它并创建一个新障碍。

abort()
将屏障置于破碎状态。这会导致任何活动或将来的调用wait()失败BrokenBarrierError。例如,如果其中一个需要中止,请使用此方法,以避免应用程序死锁。

可能最好简单地创建具有合理超时值的屏障, 以自动防止其中一个线程出错。

parties
通过障碍所需的线程数。

n_waiting
当前在屏障中等待的线程数。

broken
一个布尔值,True如果屏障处于断开状态。

异常threading.BrokenBarrierError
RuntimeError当Barrier对象被重置或损坏时,会引发 此异常(子类)。

在with语句中使用锁,条件和信号量
此模块提供的具有acquire()和 release()方法的所有对象都可以用作with 语句的上下文管理器。acquire()输入块时将调用该方法,并release()在退出块时调用该方法。因此,以下片段:

with some_lock:
    # do something...
相当于:

some_lock.acquire()
try:
    # do something...
finally:
    some_lock.release()
目前Lock,RLock,Condition, Semaphore,和BoundedSemaphore对象可以用作 with声明上下文管理。        
    
官方解释

  thread 较低级别线程

该模块提供了用于处理多个线程(也称为轻量级进程或任务)的低级原语- 多个控制线程共享其全局数据空间。为了同步,提供了简单的锁(也称为互斥锁或二进制信号量)。该threading模块提供了一个基于此模块构建的更易于使用和更高级别的线程API。

在版本3.7中更改:此模块曾经是可选的,现在始终可用。

该模块定义了以下常量和函数:

异常_thread.error
引发特定于线程的错误。

在版本3.3中更改:现在是内置的同义词RuntimeError。

_thread.LockType
这是锁定对象的类型。

_thread.start_new_thread(函数,args [,kwargs ] )
启动一个新线程并返回其标识符。线程使用参数列表args(必须是元组)执行函数 函数。可选的 kwargs参数指定关键字参数的字典。当函数返回时,线程将以静默方式退出。当函数以未处理的异常终止时,将打印堆栈跟踪,然后线程退出(但其他线程继续运行)。

_thread.interrupt_main()
KeyboardInterrupt在主线程中引发异常。子线程可以使用此函数来中断主线程。

_thread.exit()
举起SystemExit例外。未捕获时,这将导致线程以静默方式退出。

_thread.allocate_lock()
返回一个新的锁定对象。锁的方法如下所述。锁最初是解锁的。

_thread.get_ident()
返回当前线程的“线程标识符”。这是一个非零整数。它的价值没有直接意义; 它旨在用作例如索引线程特定数据的字典的魔术cookie。当线程退出并创建另一个线程时,线程标识符可以被回收。

_thread.stack_size([ 大小] )
返回创建新线程时使用的线程堆栈大小。可选的 size参数指定用于后续创建的线程的堆栈大小,并且必须为0(使用平台或配置的默认值)或至少为32,768(32 KiB)的正整数值。如果未指定大小,则使用0。如果不支持更改线程堆栈大小,RuntimeError则引发a。如果指定的堆栈大小无效,则aValueError被提升,堆栈大小未经修改。32 KiB是目前支持的最小堆栈大小值,以保证解释器本身有足够的堆栈空间。请注意,某些平台可能对堆栈大小的值有特定限制,例如要求最小堆栈大小> 32 KiB或需要以系统内存页面大小的倍数进行分配 - 应提供平台文档以获取更多信息(4 KiB页面)很常见;在没有更具体信息的情况下,建议的方法是使用4096的倍数作为堆栈大小。

可用性:Windows,具有POSIX线程的系统。

_thread.TIMEOUT_MAX
timeout参数 允许的最大值Lock.acquire()。指定超过此值的超时将引发一个OverflowError。

版本3.2中的新功能。

锁定对象具有以下方法:

lock.acquire(waitflag = 1,超时= -1 )
在没有任何可选参数的情况下,此方法无条件地获取锁定,必要时等待另一个线程释放(一次只有一个线程可以获取锁定 - 这就是它们存在的原因)。

如果存在整数waitflag参数,则操作取决于其值:如果它为零,则仅在无需等待的情况下立即获取锁定时获取锁定,而如果它非零,则如上所述无条件地获取锁定。

如果浮点超时参数存在且为正,则它指定返回之前的最长等待时间(以秒为单位)。负 超时参数指定无限制等待。如果waitflag为零,则无法指定超时。

True如果成功获取锁定则返回值,否则 返回值False。

改变在3.2版本:该超时参数是新的。

在版本3.2中更改:现在可以通过POSIX上的信号中断锁定获取。

lock.release()
释放锁定。必须先获取锁,但不一定是同一个线程。

lock.locked()
返回锁的状态:True如果已被某个线程获取, False如果没有。

除了这些方法之外,还可以通过with语句使用锁定对象 ,例如:

import _thread

a_lock = _thread.allocate_lock()

with a_lock:
    print("a_lock is locked while this executes")
注意事项:

线程与中断奇怪地交互:KeyboardInterrupt 异常将由任意线程接收。(当signal 模块可用时,中断始终转到主线程。)
调用sys.exit()或引发SystemExit异常等同于调用_thread.exit()。
无法acquire()在锁定时中断该方法 - KeyboardInterrupt获取锁定后将发生异常。
当主线程退出时,系统定义其他线程是否存活。在大多数系统上,它们在不执行 try... finally子句或执行对象析构函数的情况下被终止。
当主线程退出时,它不执行任何常规清理(除了try... finally子句被尊重),并且不刷新标准I / O文件。
官方解释

  queue--同步队列类

该queue模块实现了多生产者,多消费者队列。当必须在多个线程之间安全地交换信息时,它在线程编程中特别有用。Queue此模块中的类实现了所有必需的锁定语义。这取决于Python中线程支持的可用性; 看threading 模块。

该模块实现了三种类型的队列,它们的区别仅在于检索条目的顺序。在FIFO 队列中,添加的第一个任务是第一个检索的任务。在 LIFO队列中,最近添加的条目是第一个检索的(像堆栈一样运行)。使用优先级队列,条目将保持排序(使用heapq模块),并首先检索最低值的条目。

在内部,这三种类型的队列使用锁来临时阻止竞争线程; 但是,它们并非旨在处理线程内的重入。

此外,该模块实现了“简单” FIFO队列类型SimpleQueue,其具体实现提供了额外的保证以换取更小的功能。

该queue模块定义了以下类和异常:

class queue.Queue(maxsize = 0 )
FIFO队列的构造函数。 maxsize是一个整数,用于设置可以放入队列的项目数的上限。达到此大小后,插入将阻止,直到消耗队列项。如果 maxsize小于或等于零,则队列大小为无限大。

class queue.LifoQueue(maxsize = 0 )
LIFO队列的构造函数。 maxsize是一个整数,用于设置可以放入队列的项目数的上限。达到此大小后,插入将阻止,直到消耗队列项。如果 maxsize小于或等于零,则队列大小为无限大。

class queue.PriorityQueue(maxsize = 0 )
优先级队列的构造函数。 maxsize是一个整数,用于设置可以放入队列的项目数的上限。达到此大小后,插入将阻止,直到消耗队列项。如果 maxsize小于或等于零,则队列大小为无限大。

首先检索最低值的条目(最低值条目是返回的条目sorted(list(entries))[0])。条目的典型模式是以下形式的元组:。(priority_number, data)

如果数据元素不具有可比性,则可以将数据包装在忽略数据项的类中,并仅比较优先级编号:

from dataclasses import dataclass, field
from typing import Any

@dataclass(order=True)
class PrioritizedItem:
    priority: int
    item: Any=field(compare=False)
官方解释

  线程的创建Threading.Thread 类                                     

  线程的创建                                      

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=("hahage",))
    t.start()
    print("主线程.....")

# 结果是:
# 主线程.....
# 这个地方  等待2秒
# hahage say hello 
创建线程的方式1
from threading import Thread
import time

class Sayhi(Thread):

    def __init__(self,name):
        super().__init__()
        self.name = name

    def run(self):

        time.sleep(2)
        print("%s say hello " % self.name)
if __name__ == '__main__':
    t = Sayhi("哈哈哥")
    t.start()
    print("主线程.....")

# 结果是:
# 主线程.....
#  这个地方 等待 2 秒
# 哈哈哥 say hello 
创建线程的方式2

  多线程 与 多进程                            

from threading import Thread
from multiprocessing import Process
import os
import time


def work():
    print("hello",os.getpid())

if __name__ == '__main__':
    # 开多个进程,每个进程都有不同的 pid
    start = time.time()
    p1 = Process(target = work)
    p2 = Process(target= work)
    p1.start()
    p2.start()
    print("主线程.../主进程...pid",os.getpid())
    end = time.time()
    print("进程需要的时间",end - start)

    # 在主进程下开启多个线程,每个线程都跟主进程的 pid 一样
    start = time.time()
    t1 = Thread(target = work)
    t2 = Thread(target = work)
    t1.start()
    t2.start()
    print("主线程/主进程pid",os.getpid())
    end = time.time()
    print("线程需要的时间",end - start)



# 结果是:
# 主线程.../主进程...pid 2684
# 进程需要的时间 0.029001474380493164
# hello 2684
# hello 2684
# 主线程/主进程pid 2684
# 线程需要的时间 0.0009999275207519531
# hello 5736
# hello 2092

# 为甚么会出现这样的情况呢????
# 因为 线程的效率 高于 进程的,虽然 进程的先执行但是 后出来
pid 的比较
from threading import Thread
from multiprocessing import Process
import os

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

    # 在主进程下开启子进程
    t = Process(target=work)
    t.start()
    print("主线程/主进程")

# 结果是:
# hello
# 主线程/主进程...
# 主线程/主进程
# hello
开启效率的比较
from threading import Thread
from multiprocessing import Process
import os

def work():
    global a
    a = 0
    print(a)

if __name__ == '__main__':
    a = 100
    t = Thread(target=work)
    t.start()
    t.join()
    print("主线程",a)

#   结果是:
# 0
#   主线程 0
# 查看结果为0,因为同一进程内的线程之间共享进程内的数据


# # 同一进程内的线程共享该进程的数据?
#     a = 100
#     p = Process(target=work)
#     p.start()
#     p.join()
#     print("主进程",a)
#
# # 结果是:
# # 0
# # 主进程 100
# # 毫无疑问子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然为100
内存数据的共享问题

  多线程实现 socket

import multiprocessing
import threading 
import socket 

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind(("127.0.0.1",8888))
s.listen(5)

def action(conn):
    while True:
        msg = conn.recv(1024)
        print(msg)
        conn.send(msg.upper())
if __name__ == '__main__':
    while 1:
        conn,addr = s.accept()
        
        p = threading.Thread(target = action,args = (conn,))
        p.start()
server
import socket

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("127.0.0.1",8888))

while 1:
    msg = input("输入内容...").strip()
    if not msg:continue
    s.send(msg.encode("utf-8"))
    date = s.recv(1024)
    print(date)
client

  Thread类的其他方法

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

threading 模块提供的一些方法:
    threading.currentThread() : 返回当前的线程变量
    threading.enumerate() : 返回一个包含正在运行的线程的 list,正在运行指线程启动后,结束前,不包括启动前和结束后的线程
    threading.cativeCount():返回正在运行的线程数量,与 
        len(threading.enumerate()) 结果是一样的
其他方法
from threading import Thread
import threading
from multiprocessing import Process
import os
import time

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


if __name__ == '__main__':
    # 在主进程下开启线程
    t = Thread(target = work)
    t.start()
    # t.setName("哈哈哥")  # 设置子线程的名字

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

#  结果是:
# MainThread
# <_MainThread(MainThread, started 5016)>
# [<_MainThread(MainThread, started 5016)>, <Thread(Thread-1, started 680)>]
# 2
# 2
# 主线程/主进程....
# Thread-1
代码示例
from threading import Thread
import time

def sayhi(name):
    time.sleep(2)
    print("%s asy hello" % name)

if __name__ == '__main__':
    t = Thread(target = sayhi,args = ("哈哈哥",))
    t.start()
    t.join()
    print("主线程.....")
    print(t.is_alive())
    print(t.isAlive())
    
# 结果是:
# 哈哈哥 asy hello
# 主线程.....
# False
# False

    t = Thread(target = sayhi,args = ("哈哈哥",))
    t.start()
    print("主线程.....")
    print(t.is_alive())
    print(t.isAlive())

# 结果是:
#主线程.....
# True
# True
# 哈哈哥 asy hello
join 方法

  守护线程:                                      

  无论是进程还是线程,都 遵循:守护XX会等待主XX 运行完毕后才销毁.

  需要强调的是:运行完毕并非终止运行

  1.对主进程来说,运行完毕指的是主进程代码运行完毕

  2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行                  完毕

1.主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被收回),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束

2.主线程在其他非守护进程运行完毕后才算运行完毕(守护进程在此时被回收).
因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束
详细解释
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 = ("哈哈哥",))
    t.setDaemon(True) # 必须在 t.start() 前设置
    t.start()

    print("主线程...")
    print(t.is_alive())

# 结果是:
# 主线程...
# True

# 子线程还没来的及 运行就结束了
守护线程 例1
from threading import Thread
import time

def foo():
    print(123)
    time.sleep(1)
    print("end 123")

def bar():
    print(456)
    time.sleep(3)
    print("end 456")

if __name__ == '__main__':
    t1 = Thread(target = foo)
    t2 = Thread(target = bar)

    t1.daemon = True
    t1.start()
    t2.start()

    print("主线程.......")

# 结果是:
# 123
# 456
# 主线程.......
# end 123
# end 456
守护线程 例2

    锁                                                                                 

      锁 与 GIL(全局解释器锁,保证每次进程中只有一个线程运行)

      同步锁                                                                                   

        既然有 GIL 锁,为什么会出现 多线程抢占资源呢??

      因为在每次线程切换的时候,如果上一个线程刚准备 对数据进行修改,那么 切换了,现在的线程也要对数据进行修改,又切换了线程,那么就出现多个线程抢占资源,这样数据就是不安全 的,那怎么办呢? 

from threading import Thread
import os
import time

def work():
    global a
    temp = a
    time.sleep(0.1)
    a = temp - 1
if __name__ == '__main__':

    a = 100
    lst = []
    for i in range(100):
        p = Thread(target = work)
        lst.append(p)
        p.start()
    for p in lst:
        p.join()
    print("最后的结果",a)

# 最后的结果 99
#  在 各个 线程运行时,因为阻塞了 0.1 秒,所以100个进程 之间切换
#  导致 数据不是0,而是 同时修改了,也就是 修改了一次
多线程抢占资源

  那就引用 同步锁:

  

import threading 

R = threading.Lock()
R.acquire()

对公共数据的操作

R.release()



with R:
    function()
View Code
from threading import Thread,Lock
import os
import time

def work():
    global a
    lock.acquire()
    temp = a
    time.sleep(0.1)
    a = temp - 1
    lock.release()
if __name__ == '__main__':
    lock = Lock()
    a = 100
    lst = []
    for i in range(100):
        p = Thread(target = work)
        lst.append(p)
        p.start()
    for p in lst:
        p.join()
    print("最后的结果",a)
#
# # 最后的结果 0
同步锁的引用
# 不加锁:并发执行,速度快,数据不安全
from threading import current_thread,Thread,Lock
import os
import time

def task():
    global a
    print("%s is running" % current_thread().getName())
    temp = a
    time.sleep(0.5)
    a = temp - 1

if __name__ == '__main__':
    a = 100
    lock = Lock()
    lst = []
    start = time.time()

    for i in range(100):
        t = Thread(target = task)
        lst.append(t)
        t.start()
    for t in lst:
        t.join()

    end = time.time()
    print("主:%s a : %s" % (end - start,a))
"""
Thread-1 is running
.......
Thread-100 is running
主:0.5260300636291504 a : 99
"""
# 不解锁:为加锁部分并发执行,加锁部分串行执行,速度慢,数据安全

from threading import current_thread,Thread,Lock
import os
import time

def task():
    print("%s is running" % current_thread().getName())
    global a
    lock.acquire()
    temp = a
    time.sleep(0.5)
    a = temp - 1
    lock.release()

if __name__ == '__main__':
    a = 100
    lock = Lock()
    lst = []
    start = time.time()

    for i in range(100):
        t = Thread(target = task)
        lst.append(t)
        t.start()
    for t in lst:
        t.join()

    end = time.time()
    print("主:%s a : %s" % (end - start,a))
"""
Thread-1 is running
.......
Thread-100 is running
主:50.11686682701111 a : 0
"""
"""
有人认为:既然加锁会让运行变成串行,那么我在 start 之后 立即使用join,那就不用锁了,
也是串行的效果啊,没错,在 start 之后使用join,肯定会将100个任务的执行变成串行,毫无疑问,
最终 a 的结果也肯定是0,是安全的,但问题是
start 后 立即join,任务内的所有代码都是串行的,而加锁,只是加锁的部分即修改共享数据的部分是串行的
单从保证数据安全方面,二者都可以实现,但很明显是加锁的效率更高
"""

from threading import current_thread,Thread,Lock
import os,time
def task():
    time.sleep(3)
    print('%s start to run' %current_thread().getName())
    global n
    temp=n
    time.sleep(0.5)
    n=temp-1


if __name__ == '__main__':
    n=100
    lock=Lock()
    start_time=time.time()
    for i in range(100):
        t=Thread(target=task)
        t.start()
        t.join()
    stop_time=time.time()
    print('主:%s n:%s' %(stop_time-start_time,n))

'''
Thread-1 start to run
Thread-2 start to run
......
Thread-100 start to run
主:350.6937336921692 n:0 #耗时是多么的恐怖
'''
互斥锁 和 join 的区别

  死锁 与 递归锁 

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

  

from threading import Thread,Lock
import time

lock = Lock()
lock.acquire()
lock.acquire()
print(123)
lock.release()
lock.release()
死锁

 解决方案:递归锁, 在python中为了支持在同一线程中多次请求同一资源,python 提供了可重入锁 RLock

这个 PLock 内部维护者一个Lock 和一个 counter变量,counter 记录了 acquire 的次数,从而使得资源可以被多次 acquire,直到一个线程所有的 acquire 都被 release,其他的线程才能获得资源,上的例子如果使用递归锁,就不会发生死锁了

from threading import RLock as Lock
import time

lock = Lock()
lock.acquire()
lock.acquire()
print(123)
lock.release()
lock.release()
递归锁 RLock

典型问题:科学家吃面

import time
from threading import Thread,Lock
noodle_lock = Lock()
fork_lock = Lock()
def eat1(name):
    noodle_lock.acquire()
    print('%s 抢到了面条'%name)
    fork_lock.acquire()
    print('%s 抢到了叉子'%name)
    print('%s 吃面'%name)
    fork_lock.release()
    noodle_lock.release()

def eat2(name):
    fork_lock.acquire()
    print('%s 抢到了叉子' % name)
    time.sleep(1)
    noodle_lock.acquire()
    print('%s 抢到了面条' % name)
    print('%s 吃面' % name)
    noodle_lock.release()
    fork_lock.release()

for name in ['哪吒','egon','yuan']:
    t1 = Thread(target=eat1,args=(name,))
    t2 = Thread(target=eat2,args=(name,))
    t1.start()
    t2.start()
死锁问题
import time
from threading import Thread,RLock
noodle_lock = fork_lock = RLock()

def eat1(name):
    noodle_lock.acquire()
    print('%s 抢到了面条'%name)
    fork_lock.acquire()
    print('%s 抢到了叉子'%name)
    print('%s 吃面'%name)
    fork_lock.release()
    noodle_lock.release()

def eat2(name):
    fork_lock.acquire()
    print('%s 抢到了叉子' % name)
    time.sleep(1)
    noodle_lock.acquire()
    print('%s 抢到了面条' % name)
    print('%s 吃面' % name)
    noodle_lock.release()
    fork_lock.release()

for name in ['哪吒','egon','yuan']:
    t1 = Thread(target=eat1,args=(name,))
    t2 = Thread(target=eat2,args=(name,))
    t1.start()
    t2.start()
递归锁解决死锁问题

线程队列

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

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

import queue

q = queue.Queue()
q.put("1")
q.put("2")
q.put("3")

print(q.get())
print(q.get())
print(q.get())
print(q.get())

# 1,2,3 先进先出 
# 但是 他会 阻塞在这,因为等你放东西进去呢
先进先出

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

 

import queue
q = queue.LifoQueue()
q.put(1)
q.put(2)
q.put(3)

print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())

# 3,2,1,
# 发现这个 队列 是不会报错的,你没东西他就等着你放
后进先出

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

import queue

q = queue.PriorityQueue()
# put 放入一个元祖,元祖的第一个元素是优先级(通常是数字,也可以是非数字之间的比较)
# 数字越小,优先级越高
q.put((20,"a"))
q.put((10,"b"))
q.put((30,"c"))

print(q.get())
print(q.get())
print(q.get())

"""
(10, 'b')
(20, 'a')
(30, 'c')
"""
优先级队列
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])。一个典型的条目模式是一个tuple形式:(优先数,数据)。



异常队列。空

对空的队列对象调用非阻塞get()(或get_nowait())时引发异常。



异常队列。已满

对已满的队列对象调用非阻塞Put()(或Put_Nowait())时引发异常。



Queue.size()

queue.empty()如果为空,则返回true

queue.full()35;如果已满则返回true

queue.put(item,block=true,timeout=none)

将项目放入队列。如果可选args块为true,超时为none(默认值),则根据需要阻塞,直到可用插槽可用。如果超时是一个正数,则它最多会阻塞超时秒,如果在该时间内没有可用插槽,则会引发完全异常。否则(block为false),如果空闲插槽立即可用,则将项目放入队列,否则引发完全异常(在这种情况下,超时将被忽略)。



排队。放下(物品)

相当于Put(项,假)。



queue.get(block=true,timeout=none)

从队列中移除并返回项目。如果可选args块为true,超时为none(默认值),则在项目可用之前根据需要进行阻止。如果超时为正数,则它最多会阻塞超时秒,如果在该时间内没有可用项,则会引发空异常。否则(block为false),如果一个项立即可用,则返回该项,否则引发空异常(在这种情况下,将忽略超时)。



queue.get_nowait()获取

等于get(false)。



提供了两种方法来支持跟踪排队的任务是否已被守护进程使用者线程完全处理。



queue.task_done()。

指示以前排队的任务已完成。由队列使用者线程使用。对于用于获取任务的每个get(),对task_done()的后续调用将告诉队列任务的处理已完成。



如果join()当前处于阻塞状态,则在处理完所有项后,它将继续运行(这意味着已将()放入队列的每个项都接收到一个task_done()调用)。



如果调用次数超过队列中放置的项的次数,则引发ValueError。



queue.join()块直接到queue被取消费用
翻译

pthon 标准模块-------concurrent.futures   ---- 启动并行任务

  该concurrent.futures模块提供了一个用于异步执行callables的高级接口。
  可以使用线程,使用ThreadPoolExecutor或单独的进程 来执行异步执行 ProcessPoolExecutor。两者都实现了相同的接口,该接口由抽象Executor类定义。

执行者对象
类concurrent.futures.Executor
一个抽象类,提供异步执行调用的方法。它不应该直接使用,而是通过其具体的子类。

submit(fn,* args,** kwargs )
将可调用的fn调度为执行, 并返回表示可调用执行的对象。fn(*args **kwargs)Future

with ThreadPoolExecutor(max_workers=1) as executor:
    future = executor.submit(pow, 323, 1235)
    print(future.result())
map(func,* iterables,timeout = None,chunksize = 1 )
与以下类似:map(func, *iterables)

在iterables收集立即而不是懒洋洋地;
func以异步方式执行,并且可以同时对func进行多次调用 。
返回的迭代器引发一个concurrent.futures.TimeoutError if __next__()被调用,结果在从原始调用到超时秒后不可用Executor.map()。 timeout可以是int或float。如果未指定 超时None,则等待时间没有限制。

如果func调用引发异常,那么当从迭代器中检索其值时,将引发该异常。

在使用时ProcessPoolExecutor,此方法将可迭代组件 切换为多个块,并将其作为单独的任务提交到池中。可以通过将chunksize设置为正整数来指定这些块的(近似)大小。对于很长的iterables,采用大值CHUNKSIZE可以显著改善性能相比的1.默认大小 ThreadPoolExecutor,CHUNKSIZE没有效果。

在3.5版中更改:添加了chunksize参数。

shutdown(wait = True )
向执行者发出信号,表示当目前待处理的期货执行完毕时,它应该释放它正在使用的任何资源。关机后拨打电话Executor.submit()并拨打电话 。Executor.map()RuntimeError

如果等待,True那么在所有待处理的期货完成执行并且与执行程序关联的资源已被释放之前,此方法将不会返回。如果等待,False则此方法将立即返回,并且当执行所有待处理的期货时,将释放与执行程序关联的资源。无论wait的值如何,整个Python程序都不会退出,直到所有待处理的期货都执行完毕。

如果使用with语句,则可以避免必须显式调用此方法 ,该语句将关闭Executor (等待,就像Executor.shutdown()使用wait set 调用一样True):

import shutil
with ThreadPoolExecutor(max_workers=4) as e:
    e.submit(shutil.copy, 'src1.txt', 'dest1.txt')
    e.submit(shutil.copy, 'src2.txt', 'dest2.txt')
    e.submit(shutil.copy, 'src3.txt', 'dest3.txt')
    e.submit(shutil.copy, 'src4.txt', 'dest4.txt')
ThreadPoolExecutor的
ThreadPoolExecutor是一个Executor子类,它使用一个线程池来异步执行调用。

当与a关联的callable Future等待另一个的结果时,可能会发生死锁Future。例如:

import time
def wait_on_b():
    time.sleep(5)
    print(b.result())  # b will never complete because it is waiting on a.
    return 5

def wait_on_a():
    time.sleep(5)
    print(a.result())  # a will never complete because it is waiting on b.
    return 6


executor = ThreadPoolExecutor(max_workers=2)
a = executor.submit(wait_on_b)
b = executor.submit(wait_on_a)
和:

def wait_on_future():
    f = executor.submit(pow, 5, 2)
    # This will never complete because there is only one worker thread and
    # it is executing this function.
    print(f.result())

executor = ThreadPoolExecutor(max_workers=1)
executor.submit(wait_on_future)
class concurrent.futures.ThreadPoolExecutor(max_workers = None,thread_name_prefix ='',initializer = None,initargs =())
Executor使用最多max_workers 线程池的子类,以异步方式执行调用。

initializer是一个可选的callable,在每个工作线程的开头调用; initargs是传递给初始化器的参数元组。如果初始化程序引发异常,则所有当前挂起的作业都会引发BrokenThreadPool,以及尝试向池中提交更多作业。

改变在3.5版本中:如果max_workers是None或者没有给出,将默认为机器上的处理器,乘以数量5,假设ThreadPoolExecutor经常使用重叠的I / O,而不是CPU的工作,工人的数量应比工人数量ProcessPoolExecutor。

在3.6版本的新功能:该thread_name_prefix加入参数,允许用户控制threading.Thread由池更容易调试创建工作线程的名字。

版本3.7中已更改:添加了初始化程序和initargs参数。

ThreadPoolExecutor示例
import concurrent.futures
import urllib.request

URLS = ['http://www.foxnews.com/',
        'http://www.cnn.com/',
        'http://europe.wsj.com/',
        'http://www.bbc.co.uk/',
        'http://some-made-up-domain.com/']

# Retrieve a single page and report the URL and contents
def load_url(url, timeout):
    with urllib.request.urlopen(url, timeout=timeout) as conn:
        return conn.read()

# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    # Start the load operations and mark each future with its URL
    future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            print('%r generated an exception: %s' % (url, exc))
        else:
            print('%r page is %d bytes' % (url, len(data)))
ProcessPoolExecutor 
本ProcessPoolExecutor类是Executor使用的过程池异步执行调用子类。 ProcessPoolExecutor使用该multiprocessing模块,它允许它侧向全局解释器锁定,但也意味着只能执行和返回可选对象。

该__main__模块必须可由工作程序子进程导入。这意味着ProcessPoolExecutor在交互式解释器中不起作用。

从提交给a的可调用调用Executor或Future方法ProcessPoolExecutor将导致死锁。

class concurrent.futures.ProcessPoolExecutor(max_workers = None,mp_context = None,initializer = None,initargs =())
Executor使用最多max_workers进程池异步执行调用的子类。如果max_workers是None或者没有给出,将默认为机器上的处理器数量。如果max_workers低于或等于0,则将ValueError 引发a。 mp_context可以是多处理上下文或None。它将用于发射工人。如果mp_context是None或者没有给出,则使用默认的多重背景。

initializer是一个可选的callable,在每个worker进程的开头调用; initargs是传递给初始化器的参数元组。如果初始化程序引发异常,则所有当前挂起的作业都将引发BrokenProcessPool,以及向池中提交更多作业的任何尝试。

版本3.3中更改:当其中一个工作进程突然终止时, BrokenProcessPool现在会引发错误。以前,行为未定义,但执行者或其未来的操作通常会冻结或死锁。

版本3.7中已更改:添加了mp_context参数以允许用户控制池创建的工作进程的start_method。

添加了初始化程序和initargs参数。

ProcessPoolExecutor示例
import concurrent.futures
import math

PRIMES = [
    112272535095293,
    112582705942171,
    112272535095293,
    115280095190773,
    115797848077099,
    1099726899285419]

def is_prime(n):
    if n < 2:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False

    sqrt_n = int(math.floor(math.sqrt(n)))
    for i in range(3, sqrt_n + 1, 2):
        if n % i == 0:
            return False
    return True

def main():
    with concurrent.futures.ProcessPoolExecutor() as executor:
        for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
            print('%d is prime: %s' % (number, prime))

if __name__ == '__main__':
    main()
未来的对象
所述Future类封装一个可调用的异步执行。 Future实例由Executor.submit()。创建。

类concurrent.futures.Future
封装可调用的异步执行。 Future 实例是由创建的Executor.submit(),不应该直接创建,除了测试。

cancel()
尝试取消通话。如果当前正在执行调用并且无法取消,则该方法将返回False,否则将取消调用并返回该方法True。

cancelled()
True如果呼叫成功取消,则返回。

running()
返回True如果当前正在执行的呼叫,无法取消。

done()
返回True如果调用成功取消或结束运行。

result(超时=无)
返回调用返回的值。如果呼叫尚未完成,则此方法将等待超时秒。如果呼叫未在超时秒内完成,则将 concurrent.futures.TimeoutError引发a。timeout可以是int或float。如果未指定超时None,则等待时间没有限制。

如果未来在完成之前取消CancelledError 则将被提出。

如果引发了调用,则此方法将引发相同的异常。

exception(超时=无)
返回通话引发的异常。如果呼叫尚未完成,则此方法将等待超时秒。如果呼叫未在超时秒内完成,则将 concurrent.futures.TimeoutError引发a。 timeout可以是int或float。如果未指定超时None,则等待时间没有限制。

如果未来在完成之前取消CancelledError 则将被提出。

如果呼叫完成而没有提高,None则返回。

add_done_callback(fn )
将可调用的fn附加到将来。 当未来被取消或完成运行时,将以未来作为唯一参数调用fn。

添加的callables按添加顺序调用,并且始终在属于添加它们的进程的线程中调用。如果callable引发Exception子类,则会记录并忽略它。如果callable引发BaseException子类,则行为未定义。

如果未来已经完成或取消,将立即调用fn。

以下Future方法适用于单元测试和 Executor实现。

set_running_or_notify_cancel()
只有Executor在执行与Future单元测试相关的工作之前,才能通过实现调用此方法。

如果方法返回False则Future取消,Future.cancel()即被调用并返回True。等待Future完成的任何线程(即通过 as_completed()或wait())将被唤醒。

如果方法返回,True那么Future未被取消并且已经处于运行状态,即调用 Future.running()将返回True。

此方法只能被调用一次后不能被称为 Future.set_result()或Future.set_exception()已被调用。

set_result(结果)
设置与相关的工作结果Future,以 结果。

此方法仅应由Executor实现和单元测试使用。

版本3.8中已更改:concurrent.futures.InvalidStateError如果Future已完成此方法,则会引发此方法 。

set_exception(例外)
设置与相关的工作结果Future的 异常。Exception

此方法仅应由Executor实现和单元测试使用。

版本3.8中已更改:concurrent.futures.InvalidStateError如果Future已完成此方法,则会引发此方法 。

模块功能
concurrent.futures.wait(fs,timeout = None,return_when = ALL_COMPLETED )
等待fs给出的Future实例(可能由不同的Executor实例创建 )完成。返回一组命名的2元组。第一组命名包含在等待完成之前完成(已完成或已取消)的期货。第二组名称包含未完成的未来。donenot_done

timeout可用于控制返回前等待的最大秒数。 timeout可以是int或float。如果未指定超时None,则等待时间没有限制。

return_when表示此函数何时返回。它必须是以下常量之一:

不变    描述
FIRST_COMPLETED    当任何未来完成或取消时,该函数将返回。
FIRST_EXCEPTION    通过引发异常,任何未来完成后,函数将返回。如果没有未来引发异常则等同于 ALL_COMPLETED。
ALL_COMPLETED    所有期货结束或取消时,该功能将返回。
concurrent.futures.as_completed(fs,timeout = None )
返回由fs给出的Future实例(可能由不同Executor实例创建)的迭代器,它们在完成(完成或被取消)时生成期货。fs复制的任何期货都将被退回一次。任何之前完成的期货 都将首先产生。返回的迭代器引发一个if 被调用,结果在从原始调用到超时秒后不可用。 timeout可以是int或float。如果 未指定超时,则等待时间没有限制。as_completed()concurrent.futures.TimeoutError__next__()as_completed()None

也可以看看
PEP 3148 - 期货 - 异步执行计算
该提议描述了此功能以包含在Python标准库中。
异常类
异常concurrent.futures.CancelledError
取消未来时提出。

异常concurrent.futures.TimeoutError
未来操作超过给定超时时引发。

异常concurrent.futures.BrokenExecutor
派生自此RuntimeError异常类是在执行程序因某种原因而中断时引发的,并且不能用于提交或执行新任务。

版本3.7中的新功能。

异常concurrent.futures.InvalidStateError
在对当前状态中不允许的未来执行操作时引发。

版本3.8中的新功能。

异常concurrent.futures.thread.BrokenThreadPool
派生 自此BrokenExecutor异常类是在其中一个工作ThreadPoolExecutor程序初始化失败时引发的。

版本3.7中的新功能。

异常concurrent.futures.process.BrokenProcessPool
派生自BrokenExecutor(以前 RuntimeError)的,当a的一个工人ProcessPoolExecutor以非干净的方式终止时(例如,如果它是从外部被杀死的话),就会引发这个异常类。

版本3.3中的新功能。
concurrent.futures 官方翻译
concurrent.futures模块提供了高度封装的异步调用接口
ThreadPoolExecutor:线程池,提供异步调用
ProcessPoolExecutor: 进程池,提供异步调用
Both implement the same interface, which is defined by the abstract Executor class.

#2 基本方法
#submit(fn, *args, **kwargs)
异步提交任务

#map(func, *iterables, timeout=None, chunksize=1) 
取代for循环submit的操作

#shutdown(wait=True) 
相当于进程池的pool.close()+pool.join()操作
wait=True,等待池内所有任务执行完毕回收完资源后才继续
wait=False,立即返回,并不会等待池内的任务执行完毕
但不管wait参数为何值,整个程序都会等到所有任务执行完毕
submit和map必须在shutdown之前

#result(timeout=None)
取得结果

#add_done_callback(fn)
回调函数

# done()
判断某一个线程是否完成

# cancle()
取消某个任务
介绍
#介绍
The ProcessPoolExecutor class is an Executor subclass that uses a pool of processes to execute calls asynchronously. ProcessPoolExecutor uses the multiprocessing module, which allows it to side-step the Global Interpreter Lock but also means that only picklable objects can be executed and returned.

class concurrent.futures.ProcessPoolExecutor(max_workers=None, mp_context=None)
An Executor subclass that executes calls asynchronously using a pool of at most max_workers processes. If max_workers is None or not given, it will default to the number of processors on the machine. If max_workers is lower or equal to 0, then a ValueError will be raised.

"""
processPoolexecutor类是一个执行器子类,它使用进程池异步执行调用。
ProcessPoolexecutor使用多处理模块,它允许它绕过全局解释器锁,
但也意味着只能执行和返回可选取的对象。
类concurrent.futures.processPoolexecutor(max_workers=none,mp_context=none)
一个执行器子类,它使用一个最多包含max-workers进程的池异步执行调用。
如果max_workers未指定或未指定,它将默认为机器上处理器的数量。
如果max_workers小于或等于0,则会引发valueerror。

"""

#用法
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

import os,time,random
def task(n):
    print('%s is runing' %os.getpid())
    time.sleep(random.randint(1,3))
    return n**2

if __name__ == '__main__':

    executor=ProcessPoolExecutor(max_workers=3)

    futures=[]
    for i in range(11):
        future=executor.submit(task,i)
        futures.append(future)
    executor.shutdown(True)
    print('+++>')
    for future in futures:
        print(future.result())
processPoolExecutor
#介绍
ThreadPoolExecutor is an Executor subclass that uses a pool of threads to execute calls asynchronously.
class concurrent.futures.ThreadPoolExecutor(max_workers=None, thread_name_prefix='')
An Executor subclass that uses a pool of at most max_workers threads to execute calls asynchronously.

Changed in version 3.5: If max_workers is None or not given, it will default to the number of processors on the machine, multiplied by 5, assuming that ThreadPoolExecutor is often used to overlap I/O instead of CPU work and the number of workers should be higher than the number of workers for ProcessPoolExecutor.

New in version 3.6: The thread_name_prefix argument was added to allow users to control the threading.Thread names for worker threads created by the pool for easier debugging.

"""
ThreadPoolExecutor是一个Executor子类,使用线程池异步执行调用。

类concurrent.futures.threadpoolExecutor(max_workers=none,thread_name_prefix='')

一个执行器子类,它使用一个最多包含max工作线程的池来异步执行调用。

在版本3.5中进行了更改:如果max_workers未指定或未指定,它将默认为计算机上处理器的数量乘以5,
假设threadpoolExecutor经常用于重叠I/O而不是CPU工作,
并且工人的数量应高于processPooleExecutor的工人数量。

版本3.6中的新功能:添加了thread_name_prefix参数,允许用户控制线程。
池创建的工作线程的线程名称,便于调试。

"""

#用法
与ProcessPoolExecutor相同
ThreadPoolExcutor
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

import os,time,random
def task(n):
    print('%s is runing' %os.getpid())
    time.sleep(random.randint(1,3))
    return n**2

if __name__ == '__main__':

    executor=ThreadPoolExecutor(max_workers=3)

    # for i in range(11):
    #     future=executor.submit(task,i)

    executor.map(task,range(1,12)) #map取代了for+submit
map 的用法
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from multiprocessing import Pool
import requests
import json
import os

def get_page(url):
    print('<进程%s> get %s' %(os.getpid(),url))
    respone=requests.get(url)
    if respone.status_code == 200:
        return {'url':url,'text':respone.text}

def parse_page(res):
    res=res.result()
    print('<进程%s> parse %s' %(os.getpid(),res['url']))
    parse_res='url:<%s> size:[%s]
' %(res['url'],len(res['text']))
    with open('db.txt','a') as f:
        f.write(parse_res)


if __name__ == '__main__':
    urls=[
        'https://www.baidu.com',
        'https://www.python.org',
        'https://www.openstack.org',
        'https://help.github.com/',
        'http://www.sina.com.cn/'
    ]

    # p=Pool(3)
    # for url in urls:
    #     p.apply_async(get_page,args=(url,),callback=pasrse_page)
    # p.close()
    # p.join()

    p=ProcessPoolExecutor(3)
    for url in urls:
        p.submit(get_page,url).add_done_callback(parse_page) #parse_page拿到的是一个future对象obj,需要用obj.result()拿到结果
回调函数
原文地址:https://www.cnblogs.com/wenqi2121/p/10507889.html