python中线程
线程
线程的实质:进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。
线程的特性:
- 同一个进程内的多个线程共享该进程内的地址资源,但也任然有自己独立的存储空间
- 创建线程的开销要远小于创建进程的开销(创建一个进程,就是创建一个车间,涉及到申请空间,而且在该空间内建至少一条流水线,但创建线程,就只是在一个车间内造一条流水线,无需申请空间,所以创建开销小)
注意:
加入你的电脑是四核的,我开四个进程的话,每一个进程占用一个cpu来运行,如果开4个线程的话 这四个线程都隶属于一个进程里面,所有4个线程只是占用一个cpu运行(伪并发,GIL锁)
因为解释性语言很难知道当前这一句执行完了下一句是什么,解释器的锅,不是python语言的锅
线程和进程的使用场景:
如果两个任务,需要共享内存,又想实现异步,使用多线程
如果两个任务,需要数据隔离,使用多进程
全局解释器锁(GIL锁)解释为什么Cpython没法使用多核优势
GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
首先确定一点:运行python文件实际上是运行python解释器的进程,每次执行python程序,都会产生一个独立的进程。例如python test.py,python aaa.py,python bbb.py会产生3个不同的python进程
解释器的代码是所有线程共享的,所以垃圾回收线程也可能访问到解释器的代码而去执行,这就导致了一个问题:对于同一个数据100,可能线程1执行x=100的同时,而垃圾回收执行的是回收100的操作,解决这种问题没有什么高明的方法,就是加锁处理,如下图的GIL,保证python解释器同一时间只能执行一个任务的代码
注:上图表示多个线程同时执行python代码,然后和垃圾回收线程一起访问解释器代码
GIL与Lock
机智的同学可能会问到这个问题:Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock?
首先,我们需要达成共识:锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据
然后,我们可以得出结论:保护不同的数据就应该加不同的锁。
最后,问题就很明朗了,GIL 与Lock是两把锁,保护的数据不一样,前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理,即Lock,如下图
有了GIL的存在,同一时刻同一进程中只有一个线程被执行
听到这里,有的同学立马质问:进程可以利用多核,但是开销大,而python的多线程开销小,但却无法利用多核优势,也就是说python没用了,php才是最牛逼的语言?
这里就回到了最开始的地方:线程和进程的使用场景:
如果两个任务,需要共享内存,又想实现异步,使用多线程
如果两个任务,需要数据隔离,使用多进程
python线程模块的选择
Python提供了几个用于多线程编程的模块,包括thread、threading和Queue等。thread和threading模块允许程序员创建和管理线程。thread模块提供了基本的线程和锁的支持,threading提供了更高级别、功能更强的线程管理的功能。Queue模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。
避免使用thread模块,因为更高级别的threading模块更为先进,对线程的支持更为完善,而且使用thread模块里的属性有可能会与threading出现冲突;其次低级别的thread模块的同步原语很少(实际上只有一个),而threading模块则有很多;再者,thread模块中当主线程结束时,所有的线程都会被强制结束掉,没有警告也不会有正常的清除工作,至少threading模块能确保重要的子线程退出后进程才退出。
thread模块不支持守护线程,当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出。而threading模块支持守护线程,守护线程一般是一个等待客户请求的服务器,如果没有客户提出请求它就在那等着,如果设定一个线程为守护线程,就表示这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。
threading模块
multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性,因而不再详细介绍
线程的创建Threading.Thread类
创建线程的方式1
1 2 3 4 5 6 7 8 9 10 | 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() print ( '主线程' ) |
创建线程的方式2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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( 'egon' ) t.start() print ( '主线程' ) |
注意:在主进程下开启多个线程,每个线程都跟主进程的pid一样,而开多个进程,每个进程都有不同的pid
t.start()和t.run(),start会自动调用run,但是调用start会自动执行run,但是不一定会立即执行,是等待调度,何时真正的被调度取决于cpu,而调用t.run则是直接执行线程对象的run方法
线程相关的基本方法和使用
Thread实例对象的方法
isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。
threading模块提供的一些方法:
threading.currentThread(): 返回当前的线程变量。
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
使用实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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 ''' |
多线程实现socket实例
服务端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import multiprocessing import threading import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.bind(( '127.0.0.1' , 8080 )) 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() |
客户端:
1 2 3 4 5 6 7 8 9 10 11 12 | import socket s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect(( '127.0.0.1' , 8080 )) while True : msg = input ( '>>: ' ).strip() if not msg: continue s.send(msg.encode( 'utf-8' )) data = s.recv( 1024 ) print (data) |
守护线程
和进程一样,主线程依旧会等待子线程的结束才结束,如果不想这样,把子线程设置成守护线程
无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁
需要强调的是:运行完毕并非终止运行
#1.对主进程来说,运行完毕指的是主进程代码运行完毕 #2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
详细解释:
#1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束, #2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | from threading import Thread import time def foo(): print ( 123 ) time.sleep( 1 ) print ( "end123" ) def bar(): print ( 456 ) time.sleep( 3 ) print ( "end456" ) t1 = Thread(target = foo) t2 = Thread(target = bar, daemon = True ) # t2.daemon=True t1.start() t2.start() print ( "main-------" ) # 123 # 456 # main------- # end123 |
死锁和递归锁
一 死锁现象
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁(A和B都拿着钥匙,但A要B的钥匙才能还锁,B要A的钥匙才能还锁,这就是死锁)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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[41m%s 拿到A锁\033[0m' % self .name) mutexB.acquire() print ( '\033[42m%s 拿到B锁\033[0m' % self .name) mutexB.release() mutexA.release() def func2( self ): mutexB.acquire() print ( '\033[43m%s 拿到B锁\033[0m' % self .name) time.sleep( 2 ) mutexA.acquire() print ( '\033[44m%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,则不会发生死锁,二者的区别是:递归锁可以连续acquire多次,而互斥锁只能acquire一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | from threading import Thread,RLock import time mutexA = mutexB = RLock() #一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止 class MyThread(Thread): def run( self ): self .func1() self .func2() def func1( self ): mutexA.acquire() print ( '\033[41m%s 拿到A锁\033[0m' % self .name) mutexB.acquire() print ( '\033[42m%s 拿到B锁\033[0m' % self .name) mutexB.release() mutexA.release() def func2( self ): mutexB.acquire() print ( '\033[43m%s 拿到B锁\033[0m' % self .name) time.sleep( 2 ) mutexA.acquire() print ( '\033[44m%s 拿到A锁\033[0m' % self .name) mutexA.release() mutexB.release() if __name__ = = '__main__' : for i in range ( 10 ): t = MyThread() t.start() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import threading class SharedCounter: def __init__( self , init_value = 0 ): self .init_value = init_value self ._value_lock = threading.Lock() def incr( self , delta = 1 ): with self ._value_lock: self .init_value + = delta def decr( self , delta = 1 ): with self ._value_lock: self .init_value - = delta |
使用递归锁,尽可能避免死锁的发生!强烈推荐使用递归锁加with大法!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import threading class SharedCounter: _lock = threading.RLock() def __init__( self , initial_value = 0 ): self ._value = initial_value def incr( self , delta = 1 ): with SharedCounter._lock: self ._value + = delta def decr( self , delta = 1 ): with SharedCounter._lock: self .incr( - delta) |
信号量
同进程的一样
Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。
实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | from threading import Thread,Semaphore import threading import time # def func(): # if sm.acquire(): # print (threading.currentThread().getName() + ' get semaphore') # time.sleep(2) # sm.release() 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() |
Event事件
同进程的一样,线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。
对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。
一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行
event.isSet():返回event的状态值;
event.wait():如果 event.isSet()==False将阻塞线程,如果给wait传值,那么就代表只阻塞多长时间;
event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
event.clear():恢复event的状态值为False。
例如,有多个工作线程尝试链接MySQL,我们想要在链接前确保MySQL服务正常才让那些工作线程去连接MySQL服务器,如果连接不成功,都会去尝试重新连接。那么我们就可以采用threading.Event机制来协调各个工作线程的连接操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | from threading import Thread,Event import threading import time,random def conn_mysql(): count = 1 while not event.is_set(): if count > 3 : raise TimeoutError( '链接超时' ) print ( '<%s>第%s次尝试链接' % (threading.current_thread().getName(), count)) event.wait( 0.5 ) count + = 1 print ( '<%s>链接成功' % threading.current_thread().getName()) def check_mysql(): print ( '\033[45m[%s]正在检查mysql\033[0m' % threading.current_thread().getName()) time.sleep(random.randint( 2 , 4 )) event. set () if __name__ = = '__main__' : event = Event() conn1 = Thread(target = conn_mysql) conn2 = Thread(target = conn_mysql) check = Thread(target = check_mysql) conn1.start() conn2.start() check.start() |
条件
使得线程等待,只有满足某条件时,才释放n个线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import threading def run(n): con.acquire() con.wait() # 等着 print ( "run the thread: %s" % n) con.release() if __name__ = = '__main__' : con = threading.Condition() # 条件=锁+wait的功能 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() |
定时器
定时器,指定n秒后执行某操作
1 2 3 4 5 6 7 | from threading import Timer def hello(): print ( "hello, world" ) t = Timer( 1 , hello) t.start() # after 1 seconds, "hello, world" will be printed |
验证码定时器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | from threading import Timer import random,time class Code: def __init__( self ): self .make_cache() def make_cache( self ,interval = 5 ): self .cache = self .make_code() print ( self .cache) self .t = Timer(interval, self .make_cache) self .t.start() def make_code( self ,n = 4 ): res = '' for i in range (n): s1 = str (random.randint( 0 , 9 )) s2 = chr (random.randint( 65 , 90 )) res + = random.choice([s1,s2]) return res def check( self ): while True : inp = input ( '>>: ' ).strip() if inp.upper() = = self .cache: print ( '验证成功' ,end = '\n' ) self .t.cancel() break if __name__ = = '__main__' : obj = Code() obj.check() |
线程queue
queue队列 :使用import queue,用法与进程Queue一样
queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.
- class
queue.
Queue
(maxsize=0) #先进先出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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) #last in fisrt out
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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) #存储数据时可设置优先级的队列,同数字FIFO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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') ''' |
Python标准模块--concurrent.futures实现线程池和进程池
Python标准库为我们提供了threading和multiprocessing模块编写相应的多线程/多进程代码,但是当项目达到一定的规模,频繁创建/销毁进程或者线程是非常消耗资源的,这个时候我们就要编写自己的线程池/进程池,以空间换时间。但从Python3.2开始,标准库为我们提供了concurrent.futures模块,它提供了ThreadPoolExecutor和ProcessPoolExecutor两个类,实现了对threading和multiprocessing的进一步抽象,对编写线程池/进程池提供了直接的支持。
1.Executor和Future:
concurrent.futures模块的基础是Exectuor,Executor是一个抽象类,它不能被直接使用。但是它提供的两个子类ThreadPoolExecutor和ProcessPoolExecutor却是非常有用,顾名思义两者分别被用来创建线程池和进程池的代码。我们可以将相应的tasks直接放入线程池/进程池,不需要维护Queue来操心死锁的问题,线程池/进程池会自动帮我们调度。
Future这个概念相信有java和nodejs下编程经验的朋友肯定不陌生了,你可以把它理解为一个在未来完成的操作,这是异步编程的基础,传统编程模式下比如我们操作queue.get的时候,在等待返回结果之前会产生阻塞,cpu不能让出来做其他事情,而Future的引入帮助我们在等待的这段时间可以完成其他的操作。
p.s: 如果你依然在坚守Python2.x,请先安装futures模块。
https://docs.python.org/dev/library/concurrent.futures.html
值得一提的是Executor实现了__enter__和__exit__使得其对象可以使用with操作符
池子的意义:
在刚开始学多进程或多线程时,我们迫不及待地基于多进程或多线程实现并发的套接字通信,然而这种实现方式的致命缺陷是:服务的开启的进程数或线程数都会随着并发的客户端数目地增多而增多,这会对服务端主机带来巨大的压力,甚至于不堪重负而瘫痪,于是我们必须对服务端开启的进程数或线程数加以控制,让机器在一个自己可以承受的范围内运行,这就是进程池或线程池的用途,例如进程池,就是用来存放进程的池子,本质还是基于多进程,只不过是对开启进程的数目加上了限制
注意:如果机器能够支持100个线程没有必要开10个线程的那种线程池,直接开100线程更快
开进程池和线程池都有两种方式:
第一种:
进程池——multiprocessing.Pool
线程池——multiprocessing.dummy.Pool
第二种:
进程池——from concurrent.futures import ProcessPoolExecutor
线程池——from concurrent.futures import ThreadPoolExecutor
注:第二种是对第一种的高度封装
官网:https://docs.python.org/dev/library/concurrent.futures.html
concurrent.futures模块提供了高度封装的异步调用接口
ThreadPoolExecutor:线程池,提供异步调用
ProcessPoolExecutor: 进程池,提供异步调用
Both implement the same interface, which is defined by the abstract Executor class.
基本方法
1、submit(fn, *args, **kwargs)
异步提交任务,不阻塞,不用等任务提交完就能继续往下执行,*args和**kwargs是参数
2、map(func, *iterables, timeout=None, chunksize=1)
取代for循环submit的操作
3、shutdown(wait=True)
相当于进程池的pool.close()+pool.join()操作
wait=True,等待池内所有任务执行完毕回收完资源后才继续
wait=False,立即返回,并不会等待池内的任务执行完毕
但不管wait参数为何值,整个程序都会等到所有任务执行完毕
submit和map必须在shutdown之前(shutdown之前之后不能再提交任务了)
4、result(timeout=None)
取得结果,该方法会使异步池变成同步,因为得拿到结果才能进行下一步,优化是把线程对象先放在列表里,结束后循环拿
5、add_done_callback(fn)
回调函数,这里参数接收的是函数,函数接收的不是返回值,而是future对象 pool.submit(task).add_done_callback(other_task)
6、pool.submit().done()
可以判定提交的任务是否完成,完成了返回True,否则返回done
进程池进阶
介绍
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.
用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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) # 提交任务,task是任务名,i是参数 futures.append(future) executor.shutdown( True ) print ( '+++>' ) for future in futures: print (future.result()) |
线程池使用
介绍
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.
用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 把ProcessPoolExecutor换成ThreadPoolExecutor,其余用法全部相同 from concurrent.futures import ThreadPoolExecutor from urllib2 import urlopen import time URLS = [ 'http://www.163.com' , 'https://www.baidu.com/' , 'http://qq.com/' ] def load_url(url): res = urlopen(url, timeout = 60 ) print ( '%r page is %d bytes' % (url, len (res.read()))) if __name__ = = '__main__' : start = time.time() executor = ThreadPoolExecutor(max_workers = 3 ) #使用submit方式 for url in URLS: future = executor.submit(load_url,url) #print(future.done()) print (future.result()) #加了.result()会阻塞主线程 #使用map方式 #executor.map(load_url, URLS) end = time.time() #print('主线程') print (end - start) #### |
concurrent.futures.wait:
wait方法接会返回一个tuple(元组),tuple中包含两个set(集合),一个是completed(已完成的)另外一个是uncompleted(未完成的)。使用wait方法的一个优势就是获得更大的自由度,它接收三个参数FIRST_COMPLETED, FIRST_EXCEPTION 和ALL_COMPLETE,默认设置为ALL_COMPLETED。
如果采用默认的ALL_COMPLETED,程序会阻塞直到线程池里面的所有任务都完成,再执行主线程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | from concurrent.futures import ThreadPoolExecutor,wait,as_completed import urllib.request URLS = [ 'http://www.163.com' , 'https://www.baidu.com/' , 'https://github.com/' ] def load_url(url): with urllib.request.urlopen(url, timeout = 60 ) as conn: print ( '%r page is %d bytes' % (url, len (conn.read()))) executor = ThreadPoolExecutor(max_workers = 3 ) f_list = [] for url in URLS: future = executor.submit(load_url,url) f_list.append(future) print (wait(f_list,return_when = 'ALL_COMPLETE' )) print ( '主线程' ) # 运行结果: 'http://www.163.com' page is 662047 bytes 'https://www.baidu.com/' page is 227 bytes 'https://github.com/' page is 54629 bytes DoneAndNotDoneFutures(done = {<Future at 0x2d0f898 state = finished returned NoneType>, <Future at 0x2bd0630 state = finished returned NoneType>, <Future at 0x2d27470 state = finished returned NoneType>}, not_done = set ()) 主线程 |
future
future是concurrent.futures模块和asyncio模块的重要组件
从python3.4开始标准库中有两个名为Future的类:concurrent.futures.Future和asyncio.Future
这两个类的作用相同:两个Future类的实例都表示可能完成或者尚未完成的延迟计算。与Twisted中的Deferred类、Tornado框架中的Future类的功能类似
注意:通常情况下自己不应该创建future,而是由并发框架(concurrent.futures或asyncio)实例化
原因:future表示终将发生的事情,而确定某件事情会发生的唯一方式是执行的时间已经安排好,因此只有把某件事情交给concurrent.futures.Executor子类处理时,才会创建concurrent.futures.Future实例。
如:Executor.submit()方法的参数是一个可调用的对象,调用这个方法后会为传入的可调用对象排定时间,并返回一个future
客户端代码不能应该改变future的状态,并发框架在future表示的延迟计算结束后会改变期物的状态,我们无法控制计算何时结束。
这两种future都有.done()方法,这个方法不阻塞,返回值是布尔值,指明future链接的可调用对象是否已经执行。客户端代码通常不会询问future是否运行结束,而是会等待通知。因此两个Future类都有.add_done_callback()方法,这个方法只有一个参数,类型是可调用的对象,future运行结束后会调用指定的可调用对象。
.result()方法是在两个Future类中的作用相同:返回可调用对象的结果,或者重新抛出执行可调用的对象时抛出的异常。但是如果future没有运行结束,result方法在两个Futrue类中的行为差别非常大。
对concurrent.futures.Future实例来说,调用.result()方法会阻塞调用方所在的线程,直到有结果可返回,此时,result方法可以接收可选的timeout参数,如果在指定的时间内future没有运行完毕,会抛出TimeoutError异常。
而asyncio.Future.result方法不支持设定超时时间,在获取future结果最好使用yield from结构,但是concurrent.futures.Future不能这样做
不管是asyncio还是concurrent.futures.Future都会有几个函数是返回future,其他函数则是使用future,在最开始的例子中我们使用的Executor.map就是在使用future,返回值是一个迭代器,迭代器的__next__方法调用各个future的result方法,因此我们得到的是各个futrue的结果,而不是future本身
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步