协程

一、阻塞和非阻塞

1. 程序运行中的状态

  1. New 进程正在被创建
  2. Running 进程的指令正在被执行
  3. Waiting 进程正在等待一些事件的发生(例如 I/O 的完成或者收到某个信号)
  4. Ready 进程在等待被操作系统调度
  5. Terminated 进程执行完毕(可能是被强行终止的)

2. 定义

阻塞和非阻塞描述的是进程的一个操作是否会使进程转变为等待的状态。

阻塞:程序调用了包含I/O操作的system call,因为system call涉及到了I/O操作不能立即完成,于是内核会将该程序设置为等待状态,调度其他的进程运行,等到它所请求的IO操作完成了以后,再将其改回就绪状态。

非阻塞:程序没有IO操作(或者调用了非阻塞I/O系统调用,一个非阻塞I/0系统调用不会让进程挂起),或者遇到IO通过某种手段让CPU去执行该进程的其他任务(协程)。
返回顶部

二、 同步异步

1. 定义

站在任务发布的角度

同步: 进程将任务发出去之后,等待,直到这个任务最终结束之后,给进程一个返回值,进程再在发布下一个任务

异步:进程发布一个任务后,立即得到一个返回值,就去发布下一个任务;前面发布的任务运行完毕得到结果后会给进程发送一个信号,让进程去接收结果

2. 异步 + 调用机制(爬虫)

版本一:多进程并发执行爬取数据,串行执行数据处理

from concurrent.futures import ProcessPoolExecutor
import requests
import os
import time
import random


def get(url):
    response = requests.get(url)
    print(f'{os.getpid()}正在爬取:{url}')
    time.sleep(random.randint(1, 3))
    if response.status_code == 200:
        return response.text


def parse(text):
    print(f'{os.getpid()}分析结果:{len(text)}')


url_list = [
    'http://www.taobao.com',
    'http://www.JD.com',
    'http://www.JD.com',
    'http://www.JD.com',
    'http://www.baidu.com',
    'https://www.cnblogs.com/jin-xin/articles/11232151.html',
    'https://www.cnblogs.com/jin-xin/articles/10078845.html',
    'http://www.sina.com.cn',
    'https://www.sohu.com',
    'https://www.youku.com',
]
if __name__ == '__main__':
    obj_list = []
    pool = ProcessPoolExecutor(4)
    for url in url_list:
        obj = pool.submit(get, url)
        obj_list.append(obj)
    pool.shutdown(wait=True)
    for obj in obj_list:
        parse(obj.result())

版本二:多进程并发执行爬取数据和处理数据(耦合性过高,改变数据处理方式时不方便)

from concurrent.futures import ProcessPoolExecutor
import requests
import os
import time
import random


def get(url):
    response = requests.get(url)
    print(f'{os.getpid()}正在爬取:{url}')
    time.sleep(random.randint(1, 3))
    if response.status_code == 200:
        parse(response.text)


def parse(text):
    print(f'{os.getpid()}分析结果:{len(text)}')


url_list = [
    'http://www.taobao.com',
    'http://www.JD.com',
    'http://www.JD.com',
    'http://www.JD.com',
    'http://www.baidu.com',
    'https://www.cnblogs.com/jin-xin/articles/11232151.html',
    'https://www.cnblogs.com/jin-xin/articles/10078845.html',
    'http://www.sina.com.cn',
    'https://www.sohu.com',
    'https://www.youku.com',
]
if __name__ == '__main__':
    pool = ProcessPoolExecutor(4)
    for url in url_list:
        pool.submit(get, url)
    pool.shutdown(wait=True)
    print("主")

版本三:多进程并发执行爬取数据,通过回调函数执行数据处理(通过主进程串行)

from concurrent.futures import ProcessPoolExecutor
import requests
import os
import time
import random


def get(url):
    response = requests.get(url)
    print(f'{os.getpid()}正在爬取:{url}')
    time.sleep(random.randint(1, 3))
    if response.status_code == 200:
        return response.text


def parse(obj):
    print(f'{os.getpid()}分析结果:{len(obj.result())}')


url_list = [
    'http://www.taobao.com',
    'http://www.JD.com',
    'http://www.JD.com',
    'http://www.JD.com',
    'http://www.baidu.com',
    'https://www.cnblogs.com/jin-xin/articles/11232151.html',
    'https://www.cnblogs.com/jin-xin/articles/10078845.html',
    'http://www.sina.com.cn',
    'https://www.sohu.com',
    'https://www.youku.com',
]
if __name__ == '__main__':
    pool = ProcessPoolExecutor(4)
    for url in url_list:
        obj = pool.submit(get, url)
        obj.add_done_callback(parse,)  # 主线程运行
    pool.shutdown(wait=True)
    print("主")

返回顶部

三、线程队列

1. 先进先出

import queue #不需要通过threading模块里面导入,直接import queue就可以了,这是python自带的
#用法基本和我们进程multiprocess中的queue是一样的
q=queue.Queue()
q.put('first')
q.put('second')
q.put('third')
# q.put_nowait() #没有数据就报错,可以通过try来搞
print(q.get())
print(q.get())
print(q.get())
# q.get_nowait() #没有数据就报错,可以通过try来搞
'''
结果(先进先出):
first
second
third
'''

2. 后进先出(last in first out)

import queue

q=queue.LifoQueue() #队列,类似于栈,栈我们提过吗,是不是先进后出的顺序啊
q.put('first')
q.put('second')
q.put('third')
# q.put_nowait()

print(q.get())
print(q.get())
print(q.get())
# q.get_nowait()
'''
结果(后进先出):
third
second
first
'''

3. 优先级队列

import queue

q=queue.PriorityQueue()
#put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
q.put((-10,'a'))
q.put((-5,'a'))  # 负数也可以
# q.put((20,'ws'))  #如果两个值的优先级一样,那么按照后面的值的acsii码顺序来排序,如果字符串第一个数元素相同,比较第二个元素的acsii码顺序
# q.put((20,'wd'))
# q.put((20,{'a':11})) #TypeError: unorderable types: dict() < dict() 不能是字典
# q.put((20,('w',1)))  #优先级相同的两个数据,他们后面的值必须是相同的数据类型才能比较,可以是元祖,也是通过元素的ascii码顺序来排序

q.put((20,'b'))
q.put((20,'a'))
q.put((0,'b'))
q.put((30,'c'))

print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
'''
结果(数字越小优先级越高,优先级高的优先出队):
'''

四、事件Event

import time
from threading import Thread
from threading import current_thread
from threading import Event

event = Event()  # 默认是False
def task():
    print(f'{current_thread().name} 检测服务器是否正常开启....')
    time.sleep(3)
    event.set()  # 改成了True

def task1():
    count = 1
    while count < 4:
        # event.wait()  # 轮询检测event是否为True,当其为True,继续下一行代码. 阻塞.
        event.wait(1)
        print(f'{current_thread().name} 第{count}次尝试连接服务器')
        # 设置超时时间,如果1s中以内,event改成True,代码继续执行.
        # 设置超时时间,如果超过1s中,event没做改变,代码继续执行.
        count += 1
    print(f'{current_thread().name} 连接成功')
if __name__ == '__main__':
    t1 = Thread(target=task1,)
    t2 = Thread(target=task1,)
    t3 = Thread(target=task1,)

    t = Thread(target=task)

返回顶部

五、协程

1. 协程的定义

  1. 协程的本质就是在单线程下,由用户自己控制当一个任务遇到IO阻塞了就切换另外一个任务去执行,从而来更多的获取CPU的权限,以此来提升线程的运行效率。
  2. 协程是单线程下的并发,又称微线程,纤程。英文名Coroutine。一句话说明什么是协程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。

2. 协程的优点

  1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
  2. 单线程内就可以实现并发的效果,最大限度地利用CPU

3. 协程的缺点

  1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
  2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

示例:

from gevent import monkey
monkey.patch_all() # 标记出代码中所有的IO
import threading
import gevent
import time


def eat():
    print(f'线程1{threading.current_thread().getName()}')
    print("eat food 1")
    time.sleep(3)   # monkey能够识别到time模块的sleep了
    print("eat food 2")


def play():
    print(f'线程2{threading.current_thread().getName()}')
    print("play 1")
    time.sleep(1)  # 来回切换,直到一个I/O的时间结束,这里都是我们个gevent做得,不再是控制不了的操作系统了。
    print("play 2")


g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
gevent.joinall([g1, g2])
print(f'主:{threading.current_thread().getName()}')

返回顶部

posted on 2019-08-07 20:56  回眸在曲末  阅读(60)  评论(0编辑  收藏  举报

导航