线程的实质:进程只是用来把资源集中到一块儿(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。php
线程的特性:html
注意:java
加入你的电脑是四核的,我开四个进程的话,每个进程占用一个cpu来运行,若是开4个线程的话 这四个线程都隶属于一个进程里面,全部4个线程只是占用一个cpu运行(伪并发,GIL锁)node
由于解释性语言很难知道当前这一句执行完了下一句是什么,解释器的锅,不是python语言的锅python
线程和进程的使用场景:mysql
若是两个任务,须要共享内存,又想实现异步,使用多线程git
若是两个任务,须要数据隔离,使用多进程程序员
线程小故事github
GIL本质就是一把互斥锁,既然是互斥锁,全部互斥锁的本质都同样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。sql
首先肯定一点:运行python文件其实是运行python解释器的进程,每次执行python程序,都会产生一个独立的进程。例如python test.py,python aaa.py,python bbb.py会产生3个不一样的python进程
注:上图表示多个线程同时执行python代码,而后和垃圾回收线程一块儿访问解释器代码
机智的同窗可能会问到这个问题:Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为何这里还须要lock?
首先,咱们须要达成共识:锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据
而后,咱们能够得出结论:保护不一样的数据就应该加不一样的锁。
最后,问题就很明朗了,GIL 与Lock是两把锁,保护的数据不同,前者是解释器级别的(固然保护的就是解释器级别的数据,好比垃圾回收的数据),后者是保护用户本身开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理,即Lock,以下图
有了GIL的存在,同一时刻同一进程中只有一个线程被执行
听到这里,有的同窗立马质问:进程能够利用多核,可是开销大,而python的多线程开销小,但却没法利用多核优点,也就是说python没用了,php才是最牛逼的语言?
这里就回到了最开始的地方:线程和进程的使用场景:
若是两个任务,须要共享内存,又想实现异步,使用多线程
若是两个任务,须要数据隔离,使用多进程
Python提供了几个用于多线程编程的模块,包括thread、threading和Queue等。thread和threading模块容许程序员建立和管理线程。thread模块提供了基本的线程和锁的支持,threading提供了更高级别、功能更强的线程管理的功能。Queue模块容许用户建立一个能够用于多个线程之间共享数据的队列数据结构。
避免使用thread模块,由于更高级别的threading模块更为先进,对线程的支持更为完善,并且使用thread模块里的属性有可能会与threading出现冲突;其次低级别的thread模块的同步原语不多(实际上只有一个),而threading模块则有不少;再者,thread模块中当主线程结束时,全部的线程都会被强制结束掉,没有警告也不会有正常的清除工做,至少threading模块能确保重要的子线程退出后进程才退出。
thread模块不支持守护线程,当主线程退出时,全部的子线程不论它们是否还在工做,都会被强行退出。而threading模块支持守护线程,守护线程通常是一个等待客户请求的服务器,若是没有客户提出请求它就在那等着,若是设定一个线程为守护线程,就表示这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。
threading模块
multiprocess模块的彻底模仿了threading模块的接口,两者在使用层面,有很大的类似性,于是再也不详细介绍
建立线程的方式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() print('主线程')
建立线程的方式2
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())有相同的结果。
使用实例
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 '''
服务端:
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()
客户端:
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 主线程在其余非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。由于主线程的结束意味着进程的结束,进程总体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
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的钥匙才能还锁,这就是死锁)
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一次
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()
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大法!
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):
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()
同进程的同样,线程的一个关键特性是每一个线程都是独立运行且状态不可预测。若是程序中的其 他线程须要经过判断某个线程的状态来肯定本身下一步的操做,这时线程同步问题就会变得很是棘手。为了解决这些问题,咱们须要使用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机制来协调各个工做线程的链接操做
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个线程
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秒后执行某操做
from threading import Timer def hello(): print("hello, world") t = Timer(1, hello) t.start() # after 1 seconds, "hello, world" will be printed
验证码定时器
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队列 :使用import queue,用法与进程Queue同样
queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.
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 '''
queue.
LifoQueue
(maxsize=0) #last in fisrt out
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 '''
queue.
PriorityQueue
(maxsize=0) #存储数据时可设置优先级的队列,同数字FIFO
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标准库为咱们提供了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.
基本方法
一、submit(fn, *args, **kwargs) 异步提交任务,不阻塞,不用等任务提交完就能继续往下执行,*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以前(shutdown以前以后不能再提交任务了) 四、result(timeout=None) 取得结果,该方法会使异步池变成同步,由于得拿到结果才能进行下一步,优化是把线程对象先放在列表里,结束后循环拿 五、add_done_callback(fn) 回调函数,这里参数接收的是函数,函数接收的不是返回值,而是future对象 pool.submit(task).add_done_callback(other_task)
六、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.
用法
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.
用法
把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) ####
wait方法接会返回一个tuple(元组),tuple中包含两个set(集合),一个是completed(已完成的)另一个是uncompleted(未完成的)。使用wait方法的一个优点就是得到更大的自由度,它接收三个参数FIRST_COMPLETED, FIRST_EXCEPTION 和ALL_COMPLETE,默认设置为ALL_COMPLETED。
若是采用默认的ALL_COMPLETED,程序会阻塞直到线程池里面的全部任务都完成,再执行主线程:
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是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自己