欢迎来到十九分快乐的博客

生死看淡,不服就干。

7.进程池与线程池 - 回调函数 - 协程

进程池与线程池 - 回调函数 - 协程

1.进程池与线程池

# 进程池与线程池
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import os,time,random

# 获取CPU处理器的逻辑核心数
print(os.cpu_count()) # 8  代表8核处理器

# 1. 进程池 ProcessPoolExecutor
"""
多个进程已提前在进程池中开辟了,可以并发,也可以并行
"""
def func(i):
    time.sleep(random.random())
    print(i, "执行任务~,当前进程号{}".format(os.getpid()))
    return i

if __name__ == "__main__":
    # 创建进程池对象
    """ProcessPoolExecutor(参数),默认参数是电脑硬件CPU核心数"""
    p = ProcessPoolExecutor() # 进程数量 = 参数
    # 异步提交任务
    """
    语法 :
        submit(函数,参数1,参数2,....)
    含义:
        进程池里有8个进程提前开辟好了,不要再一个一个开辟进程了,执行多个任务
    注意点:
        如果一个进程多时间内可以完成更多任务
        进程池就不会用更多进程辅助完成,可以节省系统资源
    """
    lst = []
    for i in range(10):
        obj = p.submit(func,i) #创建提交任务对象
        # obj.result() #获取函数返回值 不要加这里,会把异步变同步
        lst.append(obj)

    for i in lst:
        print(i.result(),"=1=") 

    # shutdown 等待进程池所有任务执行完毕后,在放行
    """
    result 和 shutdown 用一个就可以同步进程池和主进程
    """
    p.shutdown()
    print("进程池结束了~~")

# 2. 线程池 ThreadPoolExecutor
from threading import currentThread as ct
def func(i):
    time.sleep(0.2)
    print(i,"线程任务执行中..,当前线程号{}".format(ct().ident))
    return ct().ident
    
if __name__ == "__main__":
    # 创建线程池对象
    t = ThreadPoolExecutor() #线程数量 = 参数 * 5
    # 异步提交任务
    """
    语法 :
        submit(函数,参数1,参数2,....)
    """
    lst = []
    setvar = set()
    for i in range(100 ):
        obj = t.submit(func,i)
        lst.append(obj)
    for i in lst:
        setvar.add(i.result()) #获取线程号返回值,集合自动去重
    t.shutdown() # 等待线程池所有任务执行完毕后,在放行
    print(len(setvar)) #40 个线程 

# 3. 线程池 map 处理数据,返回迭代器
from threading import currentThread as ct

def func(i):
    time.sleep(random.random())
    print(i,"当前线程号{}".format(ct().ident))
    return i

if __name__ == "__main__":
    t = ThreadPoolExecutor()
    it = t.map(func,range(100))
    for i in it:
        print(i)
    t.shutdown()
    print("线程池执行结束~~")

"""
结论:
    无论是进程池还剩线程池,都是由固定的进程数或线程数完成的
    系统不会额外创建更多的进程或线程来完成任务
"""

2.回调函数

# 进程池与线程池的回调函数
"""
回调函数 : 回头调用一下函数

add_done_callback : 完成回收函数的调用
add_done_callback作用:
    可以在获取result返回值时,还是异步并发程序;
结论: 
    进程池的回调函数由主进程完成
    线程池的回调函数由对应子线程完成
"""
from concurrent.futures import ProcessPoolExecutor 
from concurrent.futures import ThreadPoolExecutor 
from threading import currentThread as ct
import os,time,random

# 1.进程池任务
def func(i):
    time.sleep(random.random())
    print(i,"当前进程号{}".format(os.getpid()))
    return i
    
def callback(obj):
    print("<===回调函数进程号{}===>".format(os.getpid()))
    print(obj.result())

if __name__ == "__main__":
    p = ProcessPoolExecutor()
    for i in range(20):
        obj = p.submit(func,i)
        # 异步获取当前进程返回值
        obj.add_done_callback(callback)
    p.shutdown()
    print("主进程执行结束~,进程号{}".format(os.getpid()))

# 2.线程池任务
def func(i):
    time.sleep(random.random())
    print(i,"当前线程号{}".format(ct().ident))
    return i

def callback(obj):
    print("-====回调函数线程号{}====".format(ct().ident))
    print(obj.result())
    
if __name__ == "__main__":
    t = ThreadPoolExecutor()
    for i in range(100):
        obj = t.submit(func,i)
        obj.add_done_callback(callback)
    t.shutdown()
    print("主线程号{}".format(ct().ident))

3.协程

# 协程 : 记住终极版本4就行
"""
进程是资源分配的最小单位
线程是程序调度的最小单位
协程是线程实现的最小单位
总结:
    在进程一定的情况下,开辟多个线程
    在线程一定的情况下,开辟多个协程
    提高更大的并发,充分利用CPU
"""

# 利用协程改写生产者消费者模型
# 1.迭代器
def producer():
    for i in range(100):
        yield  i
def consumer(gen):
    for i in range(10):
        print(next(gen))

gen = producer() #初始化生成器对象
consumer(gen)

# 2.greenlet 协程早期版本
"""
switch() 与到阻塞可以来回切换任务,但是需要手动完成
"""
from greenlet import greenlet
import time

def eat():
    print("吃吃~~")
    g2.switch()
    time.sleep(2)
    print("喝喝喝~")
    
def play():
    print("玩玩玩~")
    time.sleep(2)
    print("乐乐乐~")
    g1.switch()
    
g1 = greenlet(eat)
g2 = greenlet(play)
g1.switch()

# 3. gevent 版本 (遇到阻塞自动识别,但是不能识别所有阻塞)
import gevent
def eat():
    print("吃吃~~")
    # time.sleep(2) time不能识别
    gevent.sleep(2)
    print("喝喝喝~")
    
def play():
    print("玩玩玩~")
    gevent.sleep(2)
    print("乐乐乐~")
g1 = gevent.spawn(eat) #创建协程对象g1
g2 = gevent.spawn(play) #创建协程对象g2
g1.join() 
g2.join()
print("主线程结束~~")

# 4.协程的终极形态(猴子补丁)  记住这个就行
from gevent import monkey 
monkey.patch_all() #猴子补丁
"""
上面两行可以写在一行,用分号隔开
猴子补丁 : 可以把接下来引入的所有模块中的阻塞
                自动识别出来,自动遇到阻塞就切换任务
1. spawn(函数,参数1,参数2...) 创建协程
2. join 直到某个协程任务执行完毕之后,放行
3. value 获取协程任务中的返回值
4. joinall 等待所有协程任务执行完毕之后,放行(参数是一个列表)
"""
import time,gevent
def eat():
    print("吃吃~~")
    time.sleep(2)
    print("喝喝喝~")
    return "吃完了~"
    
def play():
    print("玩玩玩~")
    time.sleep(2)
    print("乐乐乐~")
    return "玩完了~"

g1 = gevent.spawn(eat) # 创建协程对象g1
g2 = gevent.spawn(play) # 创建协程对象g2
gevent.joinall([g1,g2]) # 等待所有协程任务执行完毕之后,放行
print(g1.value)
print(g2.value)
print("主线程结束~~")

4.协程案例

# 协程案例 : 爬虫
# 基本语法:
import requests
response = requests.get("http://www.baidu.com") # 返回响应对象
print(response) #<Response [200]>
print(response.status_code) #200  获取状态码 
res = response.apparent_encoding
print(res) # utf-8 获取网页中字符编码
response.encoding = res # 设置编码集,防止乱码
print(response.text) # 获取网页内容

# 爬虫
from gevent import monkey ; monkey.patch_all()
import time,gevent,requests

url_lst =[
	"http://www.baidu.com",
	"http://www.taobao.com/",
	"http://www.jd.com/",
	"http://www.4399.com/",
	"http://www.7k7k.com/",
	"http://www.baidu.com",
	"http://www.taobao.com/",
	"http://www.jd.com/",
	"http://www.4399.com/",
	"http://www.7k7k.com/",
]

# 1.正常爬取
def get_url(url):
    response = requests.get(url)
    if response.status_code == 200:
        print(response.text)
""""""
startime = time.time()
for i in url_lst:
    get_url(i)
endtime = time.time()
print("所用时间是{}".format(endtime - startime)) #所用时间是3.013963222503662

# 2.异步多协程爬取
lst = []
startime = time.time()
for i in url_lst:
    g = gevent.spawn(get_url,i)
    lst.append(g)
gevent.joinall(lst)
endtime = time.time()
print("所用时间是{}".format(endtime - startime)) #所用时间是2.8483333587646484
posted @ 2021-01-04 23:03  十九分快乐  阅读(110)  评论(0编辑  收藏  举报