1. 阻塞,非阻塞,同步,异步

进程运行的三个状态: 运行,就绪,阻塞.

从执行的角度:

阻塞: 进程运行时,遇到IO了,进程挂起,CPU被切走.

非阻塞: 进程没有遇到IO 当进程遇到IO,但我通过某种手段,让CPU强行运行我的进程

提交任务的角度:

同步: 提交一个任务,自任务开始运行直到此任务结束(可能有IO),返回一个返回值之后,我在提交下一个任务.

异步: 一次提交多个任务,然后我就直接执行下一行代码.

返回的结果,应该如何回收?

eg: 给三个老师发布任务:

​ 同步: 先告知第一个老师完成写书的任务,然后我在原地等待,等他两天之后完成了,告知我完事了,我才发布下一个任务........

​ 异步: 直接将三个任务告知三个老师,我就忙我的,知道三个老师完成之后,告知我.

2. 同步调用,异步调用

  1. 同步调用:
# 同步调用
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import time
import random
import os

def task(i):
    print(f"{os.getpid()} 开始任务")
    time.sleep(random.randint(1,3))
    print(f"{os.getpid()} 任务结束")
    return i

if __name__ == '__main__':

    # 同步调用
    pool = ProcessPoolExecutor()
    for i in range(10):
        obj = pool.submit(task,i)
        # obj是一个动态对象,返回的是当前的对象的状态,有可能运行中,可能(就绪阻塞),还可能是结束了.
        # obj.result() 必须等到这个任务完成后,返回了结果之后,再能执行下一个任务.
        print(f"任务结果:{obj.result()}")

    pool.shutdown(wait=True)

    # shutdown : 让我的主进程等待进程池中的所有子进程都结束任务之后,再执行,有点类似于join
    # shutdown : 在上一个进程池没有完成所有的任务之前,不允许添加新的任务.
    # 一个任务是通过一个函数实现的,任务完成了,他的返回值就是函数的返回值.
    print('===main===')
  1. 异步调用:
# 异步调用
# 异步调用返回值如何接收? 未解决?

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

def task(i):
    print(f"{os.getpid()} 开始任务")
    time.sleep(random.randint(1,3))
    print(f"{os.getpid()} 任务结束")
    return i

if __name__ == '__main__':

    # 异步调用
    pool = ProcessPoolExecutor()
    for i in range(10):
        pool.submit(task,i)

    pool.shutdown(wait=True)

    # shutdown : 让我的主进程等待进程池中的所有子进程都结束任务之后,再执行,有点类似于join
    # shutdown : 在上一个进程池没有完成所有的任务之前,不允许添加新的任务.
    # 一个任务是通过一个函数实现的,任务完成了,他的返回值就是函数的返回值.
    print('===main===')
  1. 异步如何取结果:

    • 方式一:

      统一回收

      # 异步调用
      # 方式一: 异步调用,统一回收结果.
      
      from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
      import time
      import random
      import os
      
      def task(i):
          print(f"{os.getpid()} 开始任务")
          time.sleep(random.randint(1,3))
          print(f"{os.getpid()} 任务结束")
          return i
      
      if __name__ == '__main__':
      
          # 异步调用
          pool = ProcessPoolExecutor()
          lst = []
          for i in range(10):
              obj = pool.submit(task,i)
              lst.append(obj)
      
          pool.shutdown(wait=True)
      
          # shutdown : 让我的主进程等待进程池中的所有子进程都结束任务之后,再执行,有点类似于join
          # shutdown : 在上一个进程池没有完成所有的任务之前,不允许添加新的任务.
          # 一个任务是通过一个函数实现的,任务完成了,他的返回值就是函数的返回值.
          print(lst)
          for i in lst:
              print(i.result())
          print('===main===')
          # 统一回收结果: 不能马上收到任何一个已经完成的任务的返回值,只能等待所有的任务全部结束统一返回回收.

3. 异步调用+回调函数

首先以爬虫引入:

浏览器工作原理: 向服务器发送一个请求,服务端验证你的请求,如果正确,给你浏览器返回一个文件,浏览器接收文件,将文件里面的代码渲染成你看到的美丽漂亮的磨样.

什么是爬虫?

  1. 利用代码模拟一个浏览器,进行浏览器的工作流程得到一堆源代码.
  2. 对源代码进行数据清洗得到我想要的数据.
import requests
response = requests.get("http://www.baidu.com")
if response.status_code == 200:
    print(response.text)

版本一:

# 版本一:
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import time
import random
import os
import requests

def task(url):
    """模拟的就是爬起多个源代码,一定会有IO操作(网络延迟)"""
    response = requests.get(url)
    if response.status_code == 200:
        return response.text

def parse(content):
    """模拟对数据的分析, 一般没有IO"""
    return len(content)

if __name__ == '__main__':
    # """串行 消耗时间长,不可取"""
    # ret = task("http://www.baidu.com")
    # print(parse(ret))
    # ret = task('http://www.JD.com')
    # print(parse(ret))
    #
    # ret = task('http://www.taobao.com')
    # print(parse(ret))
    #
    # ret = task('https://www.cnblogs.com/jin-xin/articles/7459977.html')
    # print(parse(ret))

    # 开启线程池,并发并行的执行
    url_list = [
                'http://www.baidu.com',
                'http://www.JD.com',
                'http://www.JD.com',
                'http://www.JD.com',
                'http://www.taobao.com',
                'https://www.cnblogs.com/jin-xin/articles/7459977.html',
                'https://www.luffycity.com/',
                'https://www.cnblogs.com/jin-xin/articles/9811379.html',
                'https://www.cnblogs.com/jin-xin/articles/11245654.html',
                'https://www.sina.com.cn/',
    ]
    pool = ThreadPoolExecutor(4)
    obj_list = []
    for url in url_list:
        obj = pool.submit(task,url)
        obj_list.append(obj)

    pool.shutdown(wait=True)
    for res in obj_list:
        print(parse(res.result()))

# 版本一的问题:
# 1.异步发出多个任务,并发的执行,但是统一的接收所有的任务的返回值.(效率低,不能实时的获取结果)
# 2. 分析结果流程是串行的,影响效率.

版本二:

# 版本二:
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import time
import random
import os
import requests

def task(url):
    """模拟的就是爬起多个源代码,一定会有IO操作(网络延迟)"""
    response = requests.get(url)
    if response.status_code == 200:
        return parse(response.text)

def parse(content):
    """模拟对数据的分析, 一般没有IO"""
    return len(content)
    # print(len(content))

if __name__ == '__main__':

    # 开启线程池,并发并行的执行
    url_list = [
        'http://www.baidu.com',
        'http://www.JD.com',
        'http://www.JD.com',
        'http://www.JD.com',
        'http://www.taobao.com',
        'https://www.cnblogs.com/jin-xin/articles/7459977.html',
        'https://www.luffycity.com/',
        'https://www.cnblogs.com/jin-xin/articles/9811379.html',
        'https://www.cnblogs.com/jin-xin/articles/11245654.html',
        'https://www.sina.com.cn/',
    ]
    pool = ThreadPoolExecutor(4)
    obj_list = []
    for url in url_list:
        obj = pool.submit(task, url)
        obj_list.append(obj)

    #
    pool.shutdown(wait=True)
    for res in obj_list:
        print(res.result())

现在解决问题的两个方式:

  1. 再开一个线程进程池,并发并行的处理,但是再开一个进程线程池的开销

  2. 将原来的任务扩大

    版本一:

    • 线程池设置4个线程,异步发起10个任务,每个任务是通过网页获取源码,并发执行. 最后统一用到列表回收10个任务,串行着分析源码.

    版本二:

    • 线程池设置4个线程,异步发起10个任务,每个任务是通过网页获取源码 + 数据分析,并发并行,
    • 最后将所有的结果展示出来.
    • 耦合性增强了
    • 并发执行任务,此任务最好是IO阻塞,才能发挥最大的作用.

版本三:

可以为进程池或线程池内的每个进程或线程绑定一个函数,该函数在进程或线程的任务执行完毕后自动触发,并接收任务的返回值当作参数,该函数称为回调函数

# 版本三:
# 基于异步调用回收所有任务的结果,并且要做到实时回收结果,
# 并发执行任务,每个任务只有处理IO阻塞的,不能增加新功能
# 异步调用 + 回调函数

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

def task(url):
    """模拟的就是爬起多个源代码,一定会有IO操作(网络延迟)"""
    response = requests.get(url)
    if response.status_code == 200:
        return response.text

def parse(obj):
    """模拟对数据的分析, 一般没有IO"""
    print(len(obj.result()))

if __name__ == '__main__':

    # 开启线程池,并发并行的执行
    url_list = [
        'http://www.baidu.com',
        'http://www.JD.com',
        'http://www.JD.com',
        'http://www.JD.com',
        'http://www.taobao.com',
        'https://www.cnblogs.com/jin-xin/articles/7459977.html',
        'https://www.luffycity.com/',
        'https://www.cnblogs.com/jin-xin/articles/9811379.html',
        'https://www.cnblogs.com/jin-xin/articles/11245654.html',
        'https://www.sina.com.cn/',
    ]
    pool = ThreadPoolExecutor(4)
    for url in url_list:
        obj = pool.submit(task, url)
        obj.add_done_callback(parse)
    """
    线程池设置4个线程,异步发起10个任务,每个任务是通过网页获取源代码,并发执行,
    当一个任务完成之后,将parse这个分析代码的任务交给剩余的空闲的线程去执行,这个完成了获取源代码的线程继续去处理其他任务
    如果进程池 + 回调: 回调函数由主进程去执行.
    如果线程池 + 回调: 回调函数由空闲的线程去执行.
    """

Q:

异步和回调时一回事吗?

A:不是一回事,

异步是站在发布任务的角度,一次提交多个任务,然后就直接执行下一行代码.

而回调函数,站在接收结果的角度: 按照顺序接收每个任务的结果,进行下一步处理.

回调函数: 可以为进程池或线程池内的每个进程或线程绑定一个函数,该函数在进程或线程的任务执行完毕后自动触发,并接收任务的返回值当作参数,该函数称为回调函数

异步 + 回调:

异步处理的是IO类型,回调处理的是非IO类型

我们可以把耗时间(阻塞)的任务放到进程池中,然后指定回调函数(主进程负责执行),这样主进程在执行回调函数时就省去了I/O的过程,直接拿到的是任务的结果

4. 线程queue

queue队列: 使用import queue,用法与进程Queue一样.

官方回答: queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.(当必须在多个线程之间安全地交换信息时,队列在线程编程中特别有用。)

主要在这个queue模块中通用的三个类:

  1. FIFO(先进先出) 队列
  2. LIFO(后进先出) 堆栈
  3. 优先级队列,存储数据时可设置优先级的队列,数值越小,优先级越高

第一种:

# FIFO
import queue
q = queue.Queue(3)

q.put(1)
q.put(2)
q.put(3)
# q.put(4)

print(q.get())
print(q.get())
print(q.get())
# print(q.get(block=False))

第二种:

# LIFO

import queue

q = queue.LifoQueue(4)
q.put(1)
q.put(2)
q.put("A")
q.put([1,'a'])

print(q.get())
print(q.get())
print(q.get())
print(q.get())

第三种:

# 优先级队列,存储数据时可设置优先级的队列,数值越小,优先级越高
import queue

q = queue.PriorityQueue(4)
q.put((5,"zcy"))    # 必须是元组,第一个优先级值,第二个元素
q.put((0,"zdr"))
q.put((0,"Jacky"))
q.put((-10,"zfy"))

print(q.get())
print(q.get())
print(q.get())
print(q.get())

5. 事件event

如果程序中的其他线程需要通过判断某个线程的状态来确定自己的下一步的操作,该如何设置?

第一种:

# 通过全局变量flag来控制.
from threading import Thread
from threading import current_thread
import time

flag = False

def check():
    print(f"{current_thread().name} 监测服务器是否开启...")
    time.sleep(3)
    global flag
    flag = True
    print('服务端已开启...')

def connect():
    while 1:
        print(f"{current_thread().name} 等待连接...")
        time.sleep(0.5)
        if flag:
            print(f"{current_thread().name} 连接成功...")
            break

t1 = Thread(target=check,)
t2 = Thread(target=connect,)
t1.start()
t2.start()

第二种:

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

event = Event()

def check():
    print(f"{current_thread().name} 监测服务器是否开启...")
    time.sleep(3)
    # print(event.is_set())  # 显示event的状态
    event.set()
    # print(event.is_set())  # 显示event的状态
    print('服务端已开启...')

def connect():

    print(f"{current_thread().name} 等待连接...")
    event.wait()    # 阻塞,直到event.set()方法执行之后
    # event.wait(1)   # 只阻塞1秒,1秒之后如果还没有进行set,就直接进行下一步操作.
    print(f"{current_thread().name} 连接成功...")

t1 = Thread(target=check,)
t2 = Thread(target=connect,)
t1.start()
t2.start()

小练习:一个线程监测服务器是否开始,另一个线程判断如果开始了,则显示连接成功,此线程只尝试连接3次,1s 一次,如果超过3次,还没有连接成功,则显示连接失败.

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

event = Event()

def check():
    print(f"{current_thread().name} 监测服务器是否开启...")
    time.sleep(4)
    event.set()
    print('服务端已开启...')

def connect():
    count = 3
    print(f"{current_thread().name} 等待连接...")
    while not event.is_set():
        if count:
            event.wait(1)
            print(f"还可以连接{count}次")
            count -= 1
        else:
            print('连接失败')
            return
    else:
        print(f"{current_thread().name} 连接成功...")

t1 = Thread(target=check,)
t2 = Thread(target=connect,)
t1.start()
t2.start()

百万年薪python之路 -- 并发编程之 多线程 三的更多相关文章

  1. 百万年薪python之路 -- 并发编程之 多线程 二

    1. 死锁现象与递归锁 进程也有死锁与递归锁,进程的死锁和递归锁与线程的死锁递归锁同理. 所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因为争夺资源而造成的一种互相等待的现象,在无外力的作用 ...

  2. 百万年薪python之路 -- 并发编程之 多线程 一

    多线程 1.进程: 生产者消费者模型 一种编程思想,模型,设计模式,理论等等,都是交给你一种编程的方法,以后遇到类似的情况,套用即可 生产者与消费者模型的三要素: 生产者:产生数据的 消费者:接收数据 ...

  3. 百万年薪python之路 -- 并发编程之 多进程 一

    并发编程之 多进程 一. multiprocessing模块介绍 ​ python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大 ...

  4. 百万年薪python之路 -- 并发编程之 协程

    协程 一. 协程的引入 本节的主题是基于单线程来实现并发,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发,为此我们需要先回顾下并发的本质:切换+保存状态 cpu正在运行一个任务,会在两 ...

  5. 百万年薪python之路 -- 并发编程之 多进程二

    1. 僵尸进程和孤儿进程 基于unix的环境(linux,macOS) 主进程需要等待子进程结束之后,主进程才结束 主进程时刻检测子进程的运行状态,当子进程结束之后,一段时间之内,将子进程进行回收. ...

  6. 百万年薪python之路 -- 数据库初始

    一. 数据库初始 1. 为什么要有数据库? ​ 先来一个场景: ​ 假设现在你已经是某大型互联网公司的高级程序员,让你写一个火车票购票系统,来hold住十一期间全国的购票需求,你怎么写? 由于在同一时 ...

  7. 百万年薪python之路 -- Socket

    Socket 1. 为什么学习socket 你自己现在完全可以写一些小程序了,但是前面的学习和练习,我们写的代码都是在自己的电脑上运行的,虽然我们学过了模块引入,文件引入import等等,我可以在程序 ...

  8. 百万年薪python之路 -- 面向对象之三大特性

    1.面向对象之三大特性 1.1封装 封装:就是把一堆代码和数据,放在一个空间,并且可以使用 对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象直接或者self间接获取被封 ...

  9. 百万年薪python之路 -- 面向对象之继承

    面向对象之继承 1.什么是面向对象的继承 继承(英语:inheritance)是面向对象软件技术当中的一个概念. 通俗易懂的理解是:子承父业,合法继承家产 专业的理解是:子类可以完全使用父类的方法和属 ...

随机推荐

  1. [LeetCode] 面试题之犄角旮旯 第叁章

    题库:LeetCode题库 - 中等难度 习题:网友收集 - zhizhiyu 此处应为一个简单的核心总结,以及练习笔记. 查找一个数“在不在”?桶排序理论上貌似不错. 回文问题 ----> [ ...

  2. mysql重新设置递增值

    alter table table_name AUTO_INCREMENT=value;

  3. ajax跨域访问数据

    通过json发送和接受数据,数据以json的格式在服务器端和前台进行传递,什么是json数据?这里就不进行详细阐述,轻自行百度解决. 在html5 中利用ajax 异步请求时,会遇到跨域的问题,如果域 ...

  4. 又写了两个实用的微信小程序

    忙里偷闲,最近又写了两个小程序. 一个是手机壁纸小程序,名字叫[来搜图],特点是界面干净清爽,没有多余的东西.开发这个是因为讨厌市面上那些壁纸app那样那么多的广告,真的太影响体验了.而且小程序更加轻 ...

  5. java架构之路-(spring源码篇)springIOC容器源码解析(上)

    我们这次来叭叭一下Spring的源码,这次博客主要来说说Spring源码,先粗略的撸一遍,下篇博客选几个重点去说,由于过于复杂,我也是看了一点点,我们先来过一遍源码,然后上流程图,最后我们再回头总结一 ...

  6. [scrapy-redis] install and configure scrapy-redis on CentOS 7 (1)

    0. 安装依赖 yum install -y zlib zlib-devel openssl openssl-devel bzip2 bzip2-devel sqlite-devel gcc wget ...

  7. 快学Scala 第十一课 (类继承)

    类继承: class People { } class Emp extends People{ } 和Java一样,final的类不能被继承.final的字段和方法不能被override. 在Scal ...

  8. 设计模式 - 动态代理原理及模仿JDK Proxy 写一个属于自己的动态代理

    本篇文章代码内容较多,讲的可能会有些粗糙,大家可以选择性阅读. 本篇文章的目的是简单的分析动态代理的原理及模仿JDK Proxy手写一个动态代理以及对几种代理做一个总结. 对于代理模式的介绍和讲解,网 ...

  9. 项目三:ssm仓库管理系统

    声明:项目来源于网络,尊重原创,学习使用,仅在此记录 项目介绍 ssm仓库管理系统,功能模块:客户信息管理,供应商管理,货物管理,仓库管理,仓库管理员管理,仓库出入口管理,仓库库存记录管理,系统日志管 ...

  10. Mr. Rito Post Office

    あなたは離島の郵便局に勤めるプログラマである.あなたの住んでいる地域は,複数の島々からなる.各島には一つ以上の港町がある.それらに加えて他の町や村があるかもしない.ある島から別の島に向かうためには船を ...