线程池与协程

池    标准模块(concurrent.futures) ProcessPoolExecutor   ThreadPoolExecutor

#1 介绍
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)
回调函数
 1 import os
 2 import time
 3 from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
 4 def func(i):
 5     print('thread', i ,os.getpid())
 6     time.sleep(1)
 7     print('thread %s end' % i)
 8 tp = ThreadPoolExecutor(5)
 9 if __name__ == '__main__':
10     tp.submit(func, 1)
11     tp.shutdown()
12     print('主线程', os.getpid())
13 #thread 1 12492
14 #thread 1 end
15 #主线程 12492
16 
17 import os
18 import time
19 from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
20 def func(i):
21     print('thread', i ,os.getpid())
22     time.sleep(1)
23     print('thread %s end' % i)
24 if __name__ == '__main__':
25     tp = ProcessPoolExecutor(5)
26     tp.submit(func, 1)
27     tp.shutdown()
28     print('主线程', os.getpid())
29 #thread 1 10792
30 #thread 1 end
31 #主线程 3016
32 
33 #线程中主线程与子线程的pid相同
34 #进程中主进程与子进程的pid不同
进程池和线程池与子进程pid对比
 1 import os
 2 import time
 3 from concurrent.futures import ProcessPoolExecutor
 4 def func(i):
 5     print('thread', i, os.getpid())
 6     time.sleep(1)
 7     print('thread %s end' % i)
 8     return i * '*'
 9 def call_back(arg):
10     print('call back:', os.getpid())
11     print('ret:', arg.result())
12 if __name__ == '__main__':
13     tp = ProcessPoolExecutor(5)
14     ret_lst = []
15     for i in range(20):
16         tp.submit(func, i).add_done_callback(call_back)
17     print('主线程', os.getpid())
18 #回调函数的pid和主进程中的pid相同
19 
20 import time
21 from concurrent.futures import ThreadPoolExecutor
22 from threading import current_thread as cthread
23 def func(i):
24     print('thread', i, cthread().ident)
25     time.sleep(1)
26     print('thread %s end' % i)
27     return i * '*'
28 def call_back(arg):
29     print('call back:', cthread().ident)
30     print('ret:', arg.result())
31 tp = ThreadPoolExecutor(5)
32 ret_lst = []
33 for i in range(20):
34     tp.submit(func, i).add_done_callback(call_back)
35 print('主线程', cthread().ident)
36 #回调函数的pid和子线程的pid相同
37 
38 #进程池中回调函数是主进程中完成的
39 #线程池中回调函数是子线程中完成的
回调函数的区别
 1 import time
 2 from concurrent.futures import ThreadPoolExecutor
 3 from threading import current_thread as cthread
 4 def func(i):
 5     print('thread', i, cthread().ident)
 6     time.sleep(1)
 7     print('thread %s end' % i)
 8 
 9 tp = ThreadPoolExecutor(5)
10 for i in range(20):
11     tp.submit(func, i)
12 tp.shutdown()    #close  join
13 print('主线程')
14 
15 
16 import time
17 from concurrent.futures import ThreadPoolExecutor
18 def func(i):
19     print('thread', i)
20     time.sleep(1)
21     print('thread %s end' % i)
22     return i * '*'
23 tp = ThreadPoolExecutor(5)
24 res = tp.map(func, range(20))   #map取代了for+submit
25 for i in res:
26     print(i)
map

回调函数:进程池—由主进程实现的

     线程池—由子线程实现的

线程池:实例化线程池  ThreadPoolExcutor    5*cpu_count

    异步提交任务   submit / map

    阻塞直到任务完成   shutdown

    获取子线程的返回值   result

    回调函数    add_done_callback

协程

进程:计算机中最小的资源分配单位

线程:计算机中能被CPU执行的最小单位

能开启的线程是有限的,而要处理的任务是无限的

协程:纤程(一条线程在多个任务之间来回切换)

切换这个动作是要浪费时间的,对于cpu、操作系统来说,协程是不存在的,只能看到线程

1 def consumer():
2     g = produser()
3     for num in g:
4         print(num)
5 def produser():
6     for i in range(100):
7         yield i
8 consumer()
9 #由produser任务转换到consumer任务
yield进行多任务的切换

更好的利用协程:一个线程的执行明确的切分开

当当前任务不得不陷入阻塞,在这个阻塞的过程中切换到另一个任务继续执行,只要程序还有任务要执行,当前线程永远不会阻塞

利用协程在多个任务陷入阻塞的时候进行切换来保证一个线程在处理多个任务的时候总是忙碌的,能够更加充分的利用cpu,抢占更多的时间片

无论是进程还是线程都是由操作系统来切换的,开启过多的线程或进程会给操作系统的调度增加负担

当我们使用协程的时候,协程在程序之间的切换操作系统感知不到,无论开多少个协程对操作系统来说都只是一个线程,操作系统的调度不会有任何压力

协程的本质就是一条线程,所以完全不会产生数据安全的问题

爬虫:访问过多的网页时协程可以增加效率

协程模块:greenlet(gevent的底层,切换的时候必须用switch)      gevent 直接用的,gevent可以提供更全面的功能

安装:pip3 install gevent

 1 import time
 2 import gevent
 3 def eat():
 4     print('eating 1')
 5     time.sleep(1)
 6     print('eating 2')
 7 
 8 def play():
 9     print('playing 1')
10     time.sleep(1)
11     print('playing 2')
12 
13 g1 = gevent.spawn(eat)
14 g2 = gevent.spawn(play)   
15 #自动检测阻塞事件,遇见阻塞就会进行切换,但是有些阻塞他不认识
16 g1.join()
17 g2.join()  #阻塞知道g2结束
18 
19 from gevent import time
20 import gevent
21 def eat():
22     print('eating 1')
23     time.sleep(1)
24     print('eating 2')
25 
26 def play():
27     print('playing 1')
28     time.sleep(1)
29     print('playing 2')
30 
31 g1 = gevent.spawn(eat)
32 g2 = gevent.spawn(play)
33 g1.join()
34 g2.join()
35 #eating 1      playing 1    eating 2       playing 2
36 
37 from gevent import monkey
38 monkey.patch_all()   #自动检测下面所有import的阻塞
39 import time
40 import gevent
41 
42 def eat():
43     print('eating 1')
44     time.sleep(1)
45     print('eating 2')
46 
47 def play():
48     print('playing 1')
49     time.sleep(1)
50     print('playing 2')
51 
52 g1 = gevent.spawn(eat)
53 g2 = gevent.spawn(play)
54 g1.join()
55 g2.join()
简单示例
 1 from gevent import time
 2 import gevent
 3 def eat():
 4     print('eating 1')
 5     time.sleep(3)
 6     print('eating 2')
 7 
 8 def play():
 9     print('playing 1')
10     time.sleep(1)
11     print('playing 2')
12 
13 g1 = gevent.spawn(eat)
14 g2 = gevent.spawn(play)
15 g1.join()
16 g2.join()
17 #eating 1
18 #playing 1
19 #playing 2
20 #eating 2
与阻塞时间有关

from gevent import monkey;monkey.patch_all()(下面导入的模块,自动检测内部的阻塞事件)

spawn(函数名)    产生一个协程任务,在遇到IO操作的时候帮助我们在多任务之间自动切换

join()  阻塞,直到某个任务被执行完毕

join_all()     把要阻塞的全部放进去

value 属性    获取返回值

 1 from gevent import monkey;monkey.patch_all()
 2 import time
 3 import gevent
 4 
 5 def eat():
 6     print('eating 1')
 7     time.sleep(1)
 8     print('eating 2')
 9     return 'eat finished'
10 def play():
11     print('playing 1')
12     time.sleep(1)
13     print('playing 2')
14     return 'play finished'
15 g1 = gevent.spawn(eat)
16 g2 = gevent.spawn(play)
17 gevent.joinall([g1, g2])
18 print(g1.value)
19 print(g2.value)
20 #eating 1
21 #playing 1
22 #eating 2
23 #playing 2
24 #eat finished
25 #play finished
示例

爬虫

 1 from gevent import monkey;monkey.patch_all()
 2 import time
 3 import gevent
 4 import requests
 5 
 6 url_lst = [
 7     'http://www.baidu.com',
 8     'http://www.4399.com',
 9     'http://www.7k7k.com',
10     'http://www.sogou.com',
11     'http://www.sohu.com',
12     'http://www.sina.com',
13     'http://www.jd.com',
14     'https://www.luffycity.com/home',
15     'https://www.douban.com']
16 def get_url(url):
17     response = requests.get(url)
18     if response.status_code == 200:
19         print(url, len(response.text))
20 
21 # start = time.time()
22 # for url in url_lst:
23 #     get_url(url)
24 # print(time.time()-start)
25 # 3.6362509727478027
26 
27 start = time.time()
28 g_lst = []
29 for url in url_lst:
30     g = gevent.spawn(get_url, url)
31     g_lst.append(g)
32 gevent.joinall(g_lst)
33 print(time.time()-start)
34 #0.8195478916168213
35 
36 #不可同时运行,因为内存的关系,上面的会快一些
协程爬虫对比
 1 # server
 2 from gevent import monkey;monkey.patch_all()
 3 import socket
 4 import gevent
 5 from threading import current_thread
 6 def talk(conn):
 7     print('--->', current_thread())
 8     while True:
 9         conn.send(b'hello')
10         print(conn.recv(1024))
11 
12 sk = socket.socket()
13 sk.bind(('127.0.0.1', 8520))
14 sk.listen()
15 
16 while True:
17     conn, addr = sk.accept()
18     gevent.spawn(talk,conn)
19 
20 # client
21 import socket
22 from threading import Thread
23 def client():
24     sk = socket.socket()
25     sk.connect(('127.0.0.1', 8520))
26     while True:
27         print(sk.recv(1024))
28         sk.send(b'byebye')
29 
30 for i in range(5):
31     Thread(target=client).start()
基于协程的socketserver

 

 

 

#########################################

协程
协程的本质
在一条线程中执行多个任务,每个任务执行一段时间

扩展模块
greenlet 底层的协程模块
帮助我们进行在任务之间的切换
只是单纯的切换,不能帮我们规避IO操作

gevent
能够自动帮我们检测程序中的IO操作
并且在遇到IO操作的时候能帮助我们自动切换到不阻塞的任务执行

什么样的问题适合用协程来解决
高IO、高并发

协程是用户级的切换
能够帮助我们让一条线程总是有执行要被执行
这条线程总是在就绪和运行的状态之间切换
能够抢占更多的cpu资源

线程池
concurrent.futures
ThreadPoolExcutor/ProcessPoolExcutor
submit/map
shutdown 等待池中的任务结束
result() 获取任务的返回值
add_done_callback 回调函数
子线程/主进程执行
posted @ 2018-09-26 16:42  .why  阅读(1976)  评论(0)    收藏  举报
Live2D