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. Python必备收藏!Pycharm 常用快捷键思维导图!

    本内容首发公众号[计算机视觉联盟],关注获取更多资料! 考虑到可能图片压缩,将思维导图的pdf和jpg版本都上传了百度云,大家可以下载打印一张A4纸,方便查询! 公众号后台回复关键词: 2019082 ...

  2. FPipe端口转发

    目录 0x01 FPipe介绍 0x02 端口转发 0x03 msf正向上线 注: 边界机器 win08 192.168.222.175 内网机器 win7 192.168.222.137 msf机器 ...

  3. 轻松部署calico

    一.资源 官方文档 https://docs.projectcalico.org/v3.8/getting-started/kubernetes/installation/integration 二. ...

  4. Spring 梳理-JdbcTemplate简介

    JdbcTemplate简介 Spring对数据库的操作在jdbc上面做了深层次的封装,使用spring的注入功能,可以把DataSource注册到JdbcTemplate之中. JdbcTempla ...

  5. .Net Core 商城微服务项目系列(十二):使用k8s部署商城服务

    一.简介 本篇我们将会把商城的服务部署到k8s中,同时变化的还有以下两个地方: 1.不再使用Consul做服务的注册和发现,转而使用k8s-dns来实现. 2.不再使用Ocelot作为业务网关,使用T ...

  6. layload.js的使用

    网上有人反映说lazyload只是效果好看并没有实现真正的懒加载,在后台仍然是把页面上的所有图片下了一遍,只不过是先把图片隐藏并在窗口向下滚动时再逐一显示出来罢了.lazyloag3经测试这个问题已经 ...

  7. 快学Scala 第四课 (多维数组,与Java集合的互操作)

    Scala二维数组的定义: val arr2 = Array.ofDim[String](2, 2) arr2(0)(0) = "aa" arr2(1)(0) = "bb ...

  8. MySql 8.0.12安装、配置

    1. 参考:① 菜鸟教程下载安装MySQl ② 8.0.12安装方法 以下是我遇到的问题: 2.执行 mysqd --initialize --console 后,这个时候运行突然报"无法启 ...

  9. [ASP.NET Core 3框架揭秘] 跨平台开发体验: Docker

    对于一个 .NET Core开发人员,你可能没有使用过Docker,但是你不可能没有听说过Docker.Docker是Github上最受欢迎的开源项目之一,它号称要成为所有云应用的基石,并把互联网升级 ...

  10. python编程基础之二十三

    集合:和数学里面完全一样的,不允许有重复元素,如果添加重复元素,就会被过滤,可以进行交并差的运算  集合是可变对象 本质:无需且无重复的数据结构 创建集合 s1 = set()  括号里面可以放可迭代 ...