python网络并发之线程,协程

线程

#进程是资源分配的最小单位
#线程是计算机中调度的最小单位

#线程的缘起
资源分配需要分配内存空间,分配cpu:
分配的内存空间存放着临时要处理的数据等,比如要执行的代码,数据
而这些内存空间是有限的,不能无限分配
目前配置高的主机,5万个并发已是上限.线程概念应用而生.

#线程的特点
线程是比较轻量级,能干更多的活,一个进程中的所有线程资源是共享的.
一个进程至少有一个线程在工作

线程

"""
进程是资源分配的最小单元
线程是cpu执行调度的最小单元
"""

# (1) 一个进程里包含了多个线程,线程之间是异步并发
from threading import Thread
from multiprocessing import Process
import os , time , random

"""
def func(i):
    time.sleep(random.uniform(0.1,0.9))
    print("当前进程号:{}".format(os.getpid()) , i)
    
if __name__ == "__main__":
    for i in range(10):
        t = Thread(target=func,args=(i,))
        t.start()
        
print(os.getpid())
"""

# (2) 并发的多进程和多线程之间,多线程的速度更快
# 多线程速度
def func(i):
    print( "当前进程号:{} , 参数是{} ".format(os.getpid() , i)  )

"""
if __name__ == "__main__":
    lst = []
    startime = time.time()
    for i in range(10000):
        t = Thread(target=func,args=(i,))
        t.start()
        lst.append(t)
    # print(lst)
    for i in lst:
        i.join()
    endtime = time.time()
    print("运行的时间是{}".format(endtime - startime) ) # 运行的时间是1.8805944919586182
"""
# 多进程速度
"""
if __name__ == "__main__":
    lst = []
    startime = time.time()
    for i in range(10000):
        p = Process(target=func,args=(i,))
        p.start()
        lst.append(p)
    # print(lst)
    for i in lst:
        i.join()
    endtime = time.time()
    print("运行的时间是{}".format(endtime - startime) ) # 运行的时间是101.68004035949707
"""


# (3) 多线程之间,数据共享
num = 100
lst = []
def func():
    global num
    num -= 1

for i in range(100):
    t = Thread(target=func)
    t.start()
    lst.append(t)
    
for i in lst:
    i.join()
    
print(num)
View Code

用类定义线程

from threading import Thread
import os,time

# (1)必须继承父类Thread,来自定义线程类
"""
class MyThread(Thread):

    def __init__(self,name):
        # 手动调用父类的构造方法
        super().__init__()
        # 自定义当前类需要传递的参数
        self.name = name

    def run(self):
        print(  "当前进程号{},name={}".format(os.getpid() , self.name)  )

if __name__ == "__main__":
    t = MyThread("我是线程")
    t.start()
    print( "当前进程号{}".format(os.getpid()) )
"""

# ### 线程中的相关属性
"""
# 线程.is_alive()    检测线程是否仍然存在
# 线程.setName()     设置线程名字
# 线程.getName()     获取线程名字
# 1.currentThread().ident 查看线程id号 
# 2.enumerate()        返回目前正在运行的线程列表
# 3.activeCount()      返回目前正在运行的线程数量
"""
"""
def func():
    time.sleep(1)
if __name__ == "__main__":
    t = Thread(target=func)
    t.start()
    # 检测线程是否仍然存在
    print( t.is_alive() )
    # 线程.getName()     获取线程名字
    print(t.getName())
    # 设置线程名字
    t.setName("抓API接口")
    print(t.getName())
"""


from threading import currentThread
from threading import enumerate
from threading import activeCount
def func():
    time.sleep(0.1)
    print("当前子线程号id是{},进程号{}".format( currentThread().ident ,os.getpid()) )

if __name__ == "__main__":
    t = Thread(target=func)
    t.start()
    print("当前主线程号id是{},进程号{}".format( currentThread().ident ,os.getpid()) )

    
    for i in range(5):
        t = Thread(target=func)
        t.start()
    # 返回目前正在运行的线程列表
    lst = enumerate()
    print(lst,len(lst))
    # 返回目前正在运行的线程数量 (了解)
    print(activeCount())
View Code

守护线程

# ### 守护线程 : 等待所有线程全部执行完毕之后,自己在终止程序,守护所有线程

from threading import Thread
import time
def func1():
    while True:
        time.sleep(1)        
        print("我是函数func1")
        
def func2():
    print("我是func2  start ... ")
    time.sleep(3)
    print("我是func2  end ... ")
    
def func3():
    print("我是func3 start ... ")
    time.sleep(6)    
    print("我是func3  end ... ")
    
    
if __name__ == "__main__":
    t = Thread(target=func1)
    t2 = Thread(target=func2)
    t3 = Thread(target=func3)
    
    # 设置守护线程 (启动前设置)
    t.setDaemon(True)
    
    t.start()
    t2.start()
    t3.start()
    
    print("主线程执行结束.... ")
View Code

信号量 Semaphore (线程)

"""同一时间对多个线程上多把锁"""
from threading import Thread,Semaphore
import time , random

def func(i,sem):
    time.sleep(random.uniform(0.1,0.7))
    # with语法自动实现上锁 + 解锁
    with sem:        
        print("我在电影院拉屎 .... 我是{}号".format(i))
        

if __name__ == "__main__":
    sem = Semaphore(5)
    for i in range(30):
        Thread(target=func,args=(i,sem)).start()
    print(1)
"""
    创建线程是异步的,
    上锁的过程会导致程序变成同步;
"""        
View Code

事件 Event

rom threading import Thread , Event
import time,random
"""
wait   : 动态加阻塞 (True => 放行  False => 阻塞)
is_set : 获取内部成员属性值是True 还是 False
set    : 把False -> True
clear  : 把True  -> False
"""
# (1) 基本语法
"""
e = Event()
print(e.is_set())
e.set()
print(e.is_set())
e.wait()
e.clear()
# 最多阻塞三秒,放行
e.wait(3)
print("代码执行中 ... ")
"""

# (2) 模拟连接远程数据库
"""最多连接三次,如果三次都连接不上,直接报错."""
def check(e):    
    print("目前正在检测您的账号和密码 .... ")
    # 模拟延迟的场景
    time.sleep(random.randrange(1,7)) # 1 ~ 6
    # 把成员属性值从False -> True
    e.set()

def connect(e):
    sign = False
    for i in range(1,4):
        # 最多阻塞1秒
        e.wait(1)
        if e.is_set():
            print("数据库连接成功 ... ")
            sign = True
            break
        else:
            print("尝试连接数据库第{}次失败了...".format(i))
            
    # 三次都不成功,报错
    if sign == False:
        # 主动抛出异常  超时错误
        raise TimeoutError
        

# if __name__ == "__main__":
e = Event()
t1 = Thread(target=check,args=(e,))
t1.start()


t2 = Thread(target=connect,args=(e,))
t2.start()
View Code

 

线程的缺陷

#python中的线程可以并发,但是不能并行(同一个进程下的多个线程不能分开被多个cpu同时执行)
#原因:
全局解释器锁(Cpython解释器特有) GIL锁:
同一时间,一个进程下的多个线程只能被一个cpu执行,不能实现线程的并行操作
python是解释型语言,执行一句编译一句,而不是一次性全部编译成功,不能提前规划,都是临时调度
容易造成cpu执行调度异常.所以加了一把锁叫GIL

#想要并行的解决办法:
(1)用多进程间接实现线程的并行
(2)换一个Pypy,Jpython解释器

#程序分为计算密集型和io密集型
对于计算密集型程序会过度依赖cpu,但网页,爬虫,OA办公,这种io密集型的程序里,python绰绰有余

线程中的数据安全问题

from threading import  Thread  , Lock
import time

n = 0

def func1(lock):
    global n
    
    lock.acquire() 
    for i in range(1000000):        
        n += 1
    lock.release()

def func2(lock):
    global n
    # with语法可以简化上锁+解锁的操作,自动完成
    with lock:
        for i in range(1000000):
            n -= 1

        
if __name__ == "__main__":
    lst = []
    lock = Lock()
    
    start = time.time()
    for i in range(10):
        t1 = Thread(target=func1 ,args=(lock,) )
        t1.start()
        
        t2 = Thread(target=func2 ,args=(lock,) )
        t2.start()
        
        lst.append(t1)
        lst.append(t2)

    for i in lst:
        i.join()
    # print(lst,len(lst))
    end = time.time()
    print("主线程执行结束... 当前n结果为{} ,用时{}".format(n , end-start))
View Code

 

线程相关函数

线程.is_alive() 检测线程是否仍然存在
线程.setName() 设置线程名字
线程.getName() 获取线程名字
1.currentThread().ident 查看线程id号
2.enumerate() 返回目前正在运行的线程列表
3.activeCount() 返回目前正在运行的线程数量

 

死锁,递归锁,互斥锁

加一把锁,就对应解一把锁.形成互斥锁.
从语法上来说,锁可以互相嵌套,但不要使用,
不要因为逻辑问题让上锁分成两次.导致死锁
递归锁用于解决死锁,但只是一种应急的处理办法

 

互斥锁 死锁 递归锁

from threading import Thread , Lock , RLock
import time
# (1) 语法上的死锁
"""语法上的死锁: 是连续上锁不解锁"""
"""
lock = Lock()
lock.acquire()
# lock.acquire() error
print("代码执行中 ... 1")
lock.release()
lock.release()
"""

"""是两把完全不同的锁"""
lock1 = Lock()
lock2 = Lock()

lock1.acquire()
lock2.acquire()
print("代码执行中 ... 2")
lock2.release()
lock1.release()


# (2) 逻辑上的死锁
""""""
noodles_lock = Lock()
kuaizi_lock = Lock()

def eat1(name):
    noodles_lock.acquire()
    print("{}抢到面条了 ... ".format(name))
    kuaizi_lock.acquire()
    print("{}抢到筷子了 ... ".format(name))
    
    print("开始享受香菇青菜面 ... ")
    time.sleep(0.5)
    
    kuaizi_lock.release()
    print("{}吃完了,满意的放下了筷子".format(name))
    noodles_lock.release()
    print("{}吃完了,满意的放下了面条".format(name))


def eat2(name):
    kuaizi_lock.acquire()
    print("{}抢到筷子了 ... ".format(name))
    noodles_lock.acquire()
    print("{}抢到面条了 ... ".format(name))
    
    print("开始享受香菇青菜面 ... ")
    time.sleep(0.5)    

    noodles_lock.release()
    print("{}吃完了,满意的放下了面条".format(name))
    # kuaizi_lock.release()
    print("{}吃完了,满意的放下了筷子".format(name))


if __name__ == "__main__":
    lst1 = ["康裕康","张宇"]
    lst2 = ["张保张","赵沈阳"]
    for name in lst1:
        Thread(target=eat1,args=(name,)).start()
        
    for name in lst2:
        Thread(target=eat2,args=(name,)).start()

# (3) 使用递归锁
"""
    递归锁的提出专门用来解决死锁现象
    用于快速解决线上项目死锁问题
    即使连续上锁,使用递归锁后也形同虚设,因为递归锁的作用在于解锁;
"""
"""
# 基本语法
rlock = RLock()
rlock.acquire()
rlock.acquire()
rlock.acquire()
rlock.acquire()
print("代码执行中 ... 3")
rlock.release()
rlock.release()
rlock.release()
rlock.release()
"""

"""
noodles_lock = Lock()
kuaizi_lock = Lock()

# 让noodles_lock和kuaizi_lock 都等于递归锁
noodles_lock = kuaizi_lock = RLock()

def eat1(name):
    noodles_lock.acquire()
    print("{}抢到面条了 ... ".format(name))
    kuaizi_lock.acquire()
    print("{}抢到筷子了 ... ".format(name))
    
    print("开始享受香菇青菜面 ... ")
    time.sleep(0.5)
    
    kuaizi_lock.release()
    print("{}吃完了,满意的放下了筷子".format(name))
    noodles_lock.release()
    print("{}吃完了,满意的放下了面条".format(name))


def eat2(name):
    kuaizi_lock.acquire()
    print("{}抢到筷子了 ... ".format(name))
    noodles_lock.acquire()
    print("{}抢到面条了 ... ".format(name))
    
    print("开始享受香菇青菜面 ... ")
    time.sleep(0.5)    

    noodles_lock.release()
    print("{}吃完了,满意的放下了筷子".format(name))
    kuaizi_lock.release()
    print("{}吃完了,满意的放下了筷子".format(name))


if __name__ == "__main__":
    lst1 = ["康裕康","张宇"]
    lst2 = ["张保张","赵沈阳"]
    for name in lst1:
        Thread(target=eat1,args=(name,)).start()
        
    for name in lst2:
        Thread(target=eat2,args=(name,)).start()
"""
# (4) 尽量使用一把锁解决问题,(少用锁嵌套,容易逻辑死锁)
"""
lock = Lock()
def eat1(name):

    lock.acquire()
    print("{}抢到面条了 ... ".format(name))
    print("{}抢到筷子了 ... ".format(name))
    
    print("开始享受香菇青菜面 ... ")
    time.sleep(0.5)    

    print("{}吃完了,满意的放下了筷子".format(name))    
    print("{}吃完了,满意的放下了面条".format(name))
    lock.release()
    

def eat2(name):
    lock.acquire()
    print("{}抢到筷子了 ... ".format(name))
    print("{}抢到面条了 ... ".format(name))
    
    print("开始享受香菇青菜面 ... ")
    time.sleep(0.5)    

    print("{}吃完了,满意的放下了筷子".format(name))
    print("{}吃完了,满意的放下了筷子".format(name))
    lock.release()
    

if __name__ == "__main__":
    lst1 = ["康裕康","张宇"]
    lst2 = ["张保张","赵沈阳"]
    
    for name in lst1:
        Thread(target=eat1,args=(name,)).start()
        
    for name in lst2:
        Thread(target=eat2,args=(name,)).start()
"""
View Code

 

线程队列

线程常用队列有 queue LifoQueue PriorityQueue
queue 先进先出
LifoQueue 后进先出
PriorityQueue 按照优先级排序

线程队列

from queue import Queue
"""
put 存放 超出队列长度阻塞
get 获取 超出队列长度阻塞
put_nowait 存放,超出队列长度报错
get_nowait 获取,超出队列长度报错
"""

# (1) Queue 
"""先进先出,后进先出"""
q = Queue()
q.put(100)
q.put(200)
print(q.get())
# print(q.get())
# print(q.get()) 阻塞

# print(q.get_nowait())
# print(q.get_nowait()) 报错

# Queue(3)  =>  指定队列长度, 元素个数只能是3个;
q2 = Queue(3)
q2.put(1000)
q2.put(2000)
# q2.put(3000)
# q2.put(4000) 阻塞

q2.put_nowait(6000)
# q2.put_nowait(4000) 报错


# (2) LifoQueue 
"""先进后出,后进先出(栈的特点)"""
from queue import LifoQueue
lq = LifoQueue()
lq.put(110)
lq.put(120)
lq.put(119)

print(lq.get())
print(lq.get())
print(lq.get())

# (3) PriorityQueue
"""按照优先级顺序进行排序存放(默认从小到大)"""
"""在一个优先级队列中,要放同一类型的数据,不能混合使用"""
from queue import PriorityQueue
pq = PriorityQueue()
# 1.对数字进行排序
pq.put(100)
pq.put(19)
pq.put(-90)
pq.put(88)

print(pq.get())
print(pq.get())
print(pq.get())
print(pq.get())

# 2.对字母进行排序 (按照ascii编码)
pq.put("wangwen")
pq.put("sunjian")
pq.put('wangwei')

pq.put("王文")
pq.put("孙坚")
pq.put('王维')
print( pq.get() )
print( pq.get() )
print( pq.get() )
print( pq.get() )
print( pq.get() )
print( pq.get() )

# 3.对容器进行排序
pq.put( (22,"wangwen") )
pq.put( (67,"wangyuhan") )
pq.put( (3,"anxiaodong") )
pq.put( (3,"liuyubo") )
print(pq.get())
print(pq.get())
print(pq.get())
print(pq.get())

# 4.注意点
pq.put(100)
pq.put("nihao")
pq.put( (1,2,3) )

 

线程池和进程池 (改良版)

# 线程池
# 实例化线程池 ThreadPoolExcutor (推荐5*cpu_count)
# 异步提交任务 submit / map
# 阻塞直到任务完成 shutdown
# 获取子线程的返回值 result
# 使用回调函数 add_done_callback

# 回调函数
就是一个参数,将这个函数作为参数传到另一个函数里面.
函数先执行,再执行当参数传递的这个函数,这个参数函数是回调函数

# 线程池 是由子线程实现的
# 进程池 是由主进程实现的

进程池 和 线程池

from concurrent.futures import ProcessPoolExecutor , ThreadPoolExecutor
import os,time,random

# 获取的逻辑处理器
# print(os.cpu_count())

"""多条进程提前开辟,可触发多cpu的并行效果"""
'''
# (1) 进程池 ProcessPoolExecutor
def func(i):
    # print(i)
    time.sleep(random.uniform(0.1,0.8))
    print(" 任务执行中 ...  start ... 进程号{}".format(os.getpid()) , i )
    print(" 任务执行中 ...  end ... 进程号{}".format(os.getpid()))
    return i
    
if __name__ == "__main__":
    lst = []
    # (1) 创建进程池对象
    """默认参数是 系统最大的逻辑核心数 4"""
    p = ProcessPoolExecutor()
    
    # (2) 异步提交任务
    """submit(任务,参数1,参数2 ... )"""
    """默认如果一个进程短时间内可以完成更多的任务,进程池就不会使用更多的进程来辅助完成 , 可以节省系统资源的损耗;"""
    for i in range(10):
        obj = p.submit( func , i )
        # print(obj)
        # print(obj.result()) 不要写在这,导致程序同步,内部有阻塞
        lst.append(obj)
        
    # (3) 获取当前任务的返回值
    for i in lst:
        print(i.result(),">===获取返回值===?")
        
    # (4) shutdown 等待所有进程池里的进程执行完毕之后,在放行
    p.shutdown()

    print("进程池结束 ... ")
'''
# (2) ThreadPoolExecutor
'''
# from threading import currentThread as ct
from threading import current_thread as ct

def func(i):
    print(" 任务执行中 ...  start ... 线程号{}".format( ct().ident ) , i )
    time.sleep(1)
    print(" 任务执行中 ...  end ... 线程号{}".format(os.getpid()))
    return ct().ident  # 线程号
    
if __name__ == "__main__":
    lst = []
    setvar = set()
    """默认参数是 系统最大的逻辑核心数 4 * 5 = 20"""
    
    # (1) 创建线程池对象
    t = ThreadPoolExecutor() # 20
    # print(t)
    
    # (2) 异步提交任务
    """默认如果一个线程短时间内可以完成更多的任务,线程池就不会使用更多的线程来辅助完成 , 可以节省系统资源的损耗;"""
    for i in range(100):
        obj = t.submit(func,i)
        lst.append(obj)
        
    # (3) 获取当前任务的返回值
    for i in lst:
        setvar.add(i.result())        
        
    # (4) shutdown 等待所有线程池里的线程执行完毕之后,在放行
    t.shutdown()
    print("主线程执行结束 ... ")    
    print(setvar , len(setvar))
'''

# (3) 线程池 map
from threading import currentThread as ct
from collections import Iterator,Iterable
def func(i):
    time.sleep(random.uniform(0.1,0.7))
    print("thread ... 线程号{}".format(ct().ident),i)
    return "*" * i
    
if __name__ == "__main__":
    t = ThreadPoolExecutor()
    it = t.map(func,range(100))
    # 返回的数据是迭代器
    print(isinstance(it,Iterator))
    
    # 协调子父线程,等待线程池中所有线程执行完毕之后,在放行;
    t.shutdown()
    
    # 获取迭代器里面的返回值
    for i in it:
        print(i)

    
    
"""
# 总结: 无论是进程池还是线程池,都是由固定的进程数或者线程数来执行所有任务
系统不会额外创建多余的进程或者线程来执行任务;
"""
View Code

 

回调函数

"""
回调函数: 回头调用一下函数获取最后结果
微信支付宝付款成功后, 获取付款金额
微信支付宝退款成功后, 获取退款金额
一般用在获取最后的状态值时,使用回调
通过add_done_callback最后调用一下自定义的回调函数;
"""

from concurrent.futures import ProcessPoolExecutor , ThreadPoolExecutor
from threading import currentThread as ct
import os,time,random

"""进程任务"""
def func1(i):
    time.sleep(random.uniform(0.1,0.9))
    print(" 进程任务执行中 ...  start ... 进程号{}".format(os.getpid()) , i )
    print(" 进程任务执行中 ...  end ... 进程号{}".format(os.getpid()) )
    return i
    
def call_back1(obj):
    print(   "<==回调函数的进程号{}==>".format(os.getpid())   )
    print(obj.result())
    
"""线程任务"""    
def func2(i):
    time.sleep(random.uniform(0.1,0.9))
    print(" 线程任务执行中 ...  start ... 线程号{}".format(ct().ident) , i )
    print(" 线程任务执行中 ...  end ... 线程号{}".format( ct().ident) )
    return i
    
def call_back2(obj):
    print(   "<==回调函数的线程号{}==>".format(  ct().ident) )
    print(obj.result())
    


if __name__ == "__main__":


    """        
    # (1)进程池  结果:(进程池的回调函数由主进程执行)
    p = ProcessPoolExecutor() # os.cpu_count()  => 4
    for i in range(1,11):
        obj = p.submit(func1 , i )
        # 使用add_done_callback在获取最后返回值的时候,可以异步并行
        obj.add_done_callback(call_back1)
        # 直接使用result获取返回值的时候,会变成同步程序,速度慢;
        # obj.result()
    
    p.shutdown()        
    print(   "主进程执行结束...进程号:"    ,    os.getpid()  )
    """
        
    print("<==============================================>")

    # (2)线程池  结果:(线程池的回调函数由子线程执行)
    t = ThreadPoolExecutor()
    for i in range(1,11):
        obj = t.submit(func2 , i )
        # 使用add_done_callback在获取最后返回值的时候,可以异步并发
        obj.add_done_callback(call_back2)
        # 直接使用result获取返回值的时候,会变成同步程序,速度慢;
        # obj.result()
    t.shutdown()
    print("主线程执行结束 .... 线程号{}".format(ct().ident))
    
    
    
"""
# 原型:
class Ceshi():
    def add_done_callback(self,func):
        print("系统执行操作1 ... ")
        print("系统执行操作2 ... ")
        # 回头调用一下
        func(self)
        
    def result(self):
        return 112233
    
def call_back(obj):
    print(obj.result())


obj = Ceshi()
obj.add_done_callback(call_back)
"""
View Code

 

协程(gevent)

3.#协程也叫纤程: 协程是线程的一种实现方式.
指的是一条线程能够在多任务之间来回切换的一种实现.
对于CPU、操作系统来说,协程并不存在.
任务之间的切换会花费时间.
目前电脑配置一般线程开到200会阻塞卡顿.

#协程的实现
协程帮助你记住哪个任务执行到哪个位置上了,并且实现安全的切换
一个任务一旦阻塞卡顿,立刻切换到另一个任务继续执行,保证线程总是忙碌的,更加充分的利用CPU,抢占更多的时间片
# 一个线程可以由多个协程来实现,协程之间不会产生数据安全问题

#协程模块
# greenlet gevent的底层,协程,切换的模块
# gevent 直接用的,gevent能提供更全面的功能

协程

"""
进程是资源分配的最小单位
线程是程序调度的最下单位
协程是线程实现的具体方式


总结:
在进程一定的情况下,开辟多个线程,
在线程一定的情况下,创建多个协程,
以便提高更大的并行并发
"""

# (1) 用协程改写生产者消费者模型
"""
def producer():
    for i in range(1000):
        yield i

def consumer(gen):
    for i in range(10):
        print(  next(gen)  )
        
gen = producer()
consumer(gen)
print("<==========>")
consumer(gen)
print("<==========>")
consumer(gen)
"""

# (2) greenlet 协程的早期版本
from greenlet import greenlet
import time
""" switch 可以切换任务,但是需要手动切换"""
"""
def eat():
    print("eat1")
    g2.switch()
    time.sleep(3)
    print("eat2")
    
def play():
    print("play1")    
    time.sleep(3)
    print("play2")
    g1.switch()
    
g1 = greenlet(eat)
g2 = greenlet(play)
g1.switch()
"""
# (3) 升级到gevent版本
"""自动进行任务上的切换,但是不能识别阻塞"""
"""
import gevent

def eat():
    print("eat1")
    gevent.sleep(3)
    # time.sleep(3)
    print("eat2")
    
def play():
    print("play1")
    gevent.sleep(3)    
    # time.sleep(3)
    print("play2")

# 利用gevent.spawn创建协程对象g1
g1 = gevent.spawn(eat)
# 利用gevent.spawn创建协程对象g2
g2 = gevent.spawn(play)
# 如果不加join, 主线程直接结束任务,不会默认等待协程任务.

# 阻塞,必须等待g1任务完成之后在放行
g1.join()
# 阻塞,必须等待g2任务完成之后在放行
g2.join()

print("主线程执行结束 ....  ")
"""
# (4) 协程的终极版本;
from gevent import monkey;monkey.patch_all()
"""引入猴子补丁,可以实现所有的阻塞全部识别"""

import time
import gevent

def eat():
    print("eat1")
    time.sleep(3)
    print("eat2")
    
def play():
    print("play1")
    time.sleep(3)
    print("play2")


# 利用gevent.spawn创建协程对象g1
g1 = gevent.spawn(eat)
# 利用gevent.spawn创建协程对象g2
g2 = gevent.spawn(play)
# 如果不加join, 主线程直接结束任务,不会默认等待协程任务.

# 阻塞,必须等待g1任务完成之后在放行
g1.join()
# 阻塞,必须等待g2任务完成之后在放行
g2.join()

print(" 主线程执行结束 ... ")

"""
# 分号,利用分号可以把多行代码放在一行进行编写;
a = 1
b = 2
a = 1;b = 2
"""
View Code

协程流程

 

 

协程例子

"""
# (1) spawn(函数,参数1,参数2,参数 .... ) 启动协程
# (2) join 阻塞,直到某个协程在任务执行完毕之后在放行
# (3) joinall 等待所有协程任务执行完毕之后放行;
      g1.join()  g2.join() <=> gevent.joinall( [g1,g2..] )
# (4) value 获取协程任务中的返回值 g1.value  g2.value
"""
from gevent import monkey ; monkey.patch_all()
import gevent
import time
import requests

"""
def eat():
    print("eat1 开始吃 ... ")
    time.sleep(1)
    print("eat2 继续吃 ... ")
    return "吃完了"    
    
def play():
    print("play1 开始玩 ... ")
    time.sleep(1)
    print("play2 继续玩 ... ")
    return "玩完了"

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

# (2) 利用协程爬取数据
"""
HTTP 状态码
    200 ok
    400 bad request
    404 not found
"""

"""
import requests
response = requests.get("http://www.baidu.com")
# print(response ,type(response) )

# 获取状态码
print(response.status_code)
# 获取网页中的字符编码
res = response.apparent_encoding
print(res) # utf-8
# 设置编码集,防止乱码
response.encoding = res
# 获取网页内容
res = response.text
print(res)
"""


url_lst = [
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/",
    "http://www.baidu.com",
    "http://www.jd.com/",
    "http://www.taobao.com/",
    "http://www.amazon.cn/",
    "http://www.pinduoduo.com/",
    "http://www.4399.com/"
]


def get_url(url):
    response = requests.get(url)
    if response.status_code == 200:
        # print(response.text)
        pass
        
# (1) 正常爬取
"""
startime = time.time()
for i in url_lst:
    get_url(i)
endtime = time.time()
print(endtime-startime) # 12.648817539215088
"""
# (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)) # 1秒
View Code

 

posted @ 2022-04-10 17:06  帅童学  阅读(151)  评论(0编辑  收藏  举报