day8-异步IO

创建线程的三种方式

1. 创建Thread对象 ---> target / args ---> start()
2. 继承Thread类 ---> 重写run()方法 ---> start()
3. 使用线程池(最佳选择)---> 减少频繁创建和释放线程造成的系统开销

线程间的通信非常简单,可以通过共享内存来实现
进程间的通信比较麻烦,因为进程间的内存是相互隔离的,需要使用管道、套接字等方式来通信

  • 多进程 —> 计算密集型任务 —> 科学计算 / 音视频编解码 / 加密算法

  • 多线程—>异步I/O —> I/O密集型任务 —> Web应用 / 爬虫 / 数据读写

什么时候会使用多线程???
任务耗费的时间比较长(会造成其他任务的阻塞)
任务之间没有偏序关系(一个任务不用等另一个任务执行完毕)
多线程程序的好处?
提升执行效率
改善用户体验
  • 全局解释器锁(GIL) Global Interpreter Lock

    • CPython —> 通过C实现的Python 解释器,申请和释放内存的操作并非线程安全
      所以需要GIL来保证只有一个线程能够执行关键操作,导致无法利用CPU的多核特性

      即便如此,多线程还是可以提升程序的执行效率(对于I/O密集型任务),也可以改善用户体验

      因此,对于计算密集型任务,可以使用多进程的方式突破GIL的限制

守护线程

守护线程–不值得保留的线程
守护线程 — 主线程如果结束了,守护线程即便任务没有执行完也要跟着结束

import time
from threading import Thread
from multiprocessing import Process,freeze_support


def output(content):
    while True:
        print(content,end='')
        time.sleep(0.01)


if __name__ == '__main__':
    Thread(target=output,args=('Ping',),daemon=True).start()
    Thread(target=output,args=('Pong',),daemon=True).start()
    time.sleep(2)

# daemon :主线程结束,它也结束了。不然就死循环。

银行账户类

定义一个类:一个银行账户(余额,存钱,取钱)
创建一个银行对象,启动100个线程,每个线程转入1块钱
转账完成后,查看银行账户余额。存钱和取钱的受理是需要耗费时间的

当多个线程竞争一个资源的时候,如果资源本身并不是线程安全的,那么就会出问题

如果代码中需要对数据进行保护(保护临界资源),可以使用锁机制

如果对象实现了上下文管理器协议,就可以用到wuth语法中
上下文管理器就是两个魔术方法:
    - __enter__  进入上下文的时候自动调用
    - __exit__  离开上下文的时候自动调用
"""
from threading import Thread, RLock
import time


class Account:
    """银行账户"""
    def __init__(self):
        self.balance = 0
        # 重入锁(一般情况都使用)
        self.lock = RLock()

    def deposit(self,money):
        """存钱"""
        # 如果多个线程同时执行到这个代码,那么后面的线程对余额的更新会覆盖掉之前的更新
        # 这个现象在数据库层面也有可能发生,称之为丢失更新
        # (第一类丢失更新,第二类丢失更新)

        # 通过上下文语法自动获得锁和释放锁
        with self.lock:
            new_balance = self.balance + money
            time.sleep(0.01)
            self.balance = new_balance


        # 获得锁
        # self.lock.acquire()
        # try:
        #     new_balance = self.balance +money
        #     time.sleep(0.01)
        #     self.balance = new_balance
        # finally:  # 总是执行
        #     # 释放锁
        #     self.lock.release()

    def withdraw(self,money):
        """取钱"""
        with self.lock:
            if money <= self.balance:
                new_balance = self.balance - money
                time.sleep(0.01)
                self.balance = new_balance
                return True
            return False

if __name__ == '__main__':
    account = Account()
    account.balance = 100
    add_money_threads = []
    for _ in range(100):
        t = Thread(target=account.withdraw,args=(1,))
        add_money_threads.append(t)
        t.start()
    for t in add_money_threads:
        t.join()

    print(account.balance)

池化的思想和重入锁

"""
创建和释放一个线程都会产生比较大的系统开销,所以如果程序要长时间使用多个线程
最好的方式是通过线程池来使用线程。
线程池会提前创建好若干个线程,在使用过程中不需要创建和释放,程序和线程池之间是借还关系

池化的思想:空间换时间的技术
"""
import time
from concurrent.futures.thread import ThreadPoolExecutor
from threading import RLock


class Account:
    """银行账户"""
    def __init__(self):
        self.balance = 0
        # 重入锁(一般情况都使用)
        self.lock = RLock()

    def deposit(self,money):
        """存钱"""
        # 如果多个线程同时执行到这个代码,那么后面的线程对余额的更新会覆盖掉之前的更新
        # 这个现象在数据库层面也有可能发生,称之为丢失更新
        # (第一类丢失更新,第二类丢失更新)

        # 通过上下文语法自动获得锁和释放锁
        with self.lock:
            new_balance = self.balance + money
            time.sleep(0.01)
            self.balance = new_balance


    def withdraw(self,money):
        """取钱"""
        with self.lock:
            if money <= self.balance:
                new_balance = self.balance - money
                time.sleep(0.01)
                self.balance = new_balance
                return True
            return False

if __name__ == '__main__':
    account = Account()
    account.balance = 100

    # 创建线程池 (多线程)
    with ThreadPoolExecutor(max_workers=16) as pool:
        for _ in range(100):
            # 如果传入的函数有多个参数,可以直接依次传入,不需要用元组包装
            # print('准备存入')
            pool.submit(account.deposit,1)
            # print('准备取钱')
            pool.submit(account.withdraw,1)


    # pool.shutdown() # 线程池中的任务完成后才结束

    print(account.balance)

I/O操作模式

  • 同步:调用一个函数,必须等函数返回以后代码才能往回执行
    同步阻塞:
    同步非阻塞:

  • 异步:调用一个函数,不必等函数返回以后代码才能往回执行
    异步阻塞: — 多路I/O复用
    异步非阻塞: — 回调式I/O —并发

  • python做并发(并行:必须在同一时间开始)编程的三种方式:
    多线程:
    多进程:
    异步I/O: --> 协作式并发(通过多个子程序相互协作,提高CPU利用率、

  • 迭代器:实现了迭代器协议的对象

    • 迭代器协议对应两个魔术方法:
      iter :获取迭代器对象
      next :获取到下一个值

斐波那契数列迭代器

# 迭代器
class FibIter:

    def __init__(self, num):
        self.a, self.b = 0, 1
        self.num = num
        self.counter = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.counter < self.num:
            self.a, self.b = self.b, self.a+self.b
            self.counter += 1
            return self.a

        raise StopIteration()   # 抛异常来停止迭代



if __name__ == '__main__':
    fiter = FibIter(20)
    for value in fiter:
        print(value)

生成器

"""
生成器:迭代器语法升级版本

"""
# 生成器
def fib(num):
    a, b = 0, 1
    for _ in range(num):
        a, b = b, a + b
        yield a

if __name__ == '__main__':
    fiter = fib(20)
    for value in fiter:
        print(value)

协程

"""
协程(coroutine):相互协作的子程序
生成器经过预激活操作就可以升级为一个协程

yield ---> 产出、屈服,让步
"""


# yield和 send 协作

def callc_avg():
    total, counter = 0, 0
    avg_value = 0
    while True:
        curr_num = yield avg_value  # 生成器遇到yield又交回控制权
        total += curr_num
        counter += 1
        avg_value = total / counter

def main():
    gen = callc_avg()
    # 将生成器预激活为  协程  ---  必须激活
    # next(gen)
    gen.send(None)  # 激活必须是None  # send 数据发给 yield


    for _ in range(3):
        num = int(input())
        print(gen.send(num))  # send 数据发给 yield


if __name__ == '__main__':
   main()

使用异步函数来创建协程对象

import asyncio

"""
使用异步函数来创建协程对象

async  --- asynchronous
sync   --- synchronous

"""

# 定义异步函数
async def say_hello():
    print('hello')

# 调用异步函数不是执行函数体,而是得到一个协程对象
co = say_hello()
print(co)  # <coroutine object say_hello at 0x00000246FFADFEC8>
# 协程对象需要跟其他对象协作
# 协程对象需要挂载到一个事件循环上才能运转起来
loop = asyncio.get_event_loop()
loop.run_until_complete(co)  # hello
loop.close()

同步阻塞式调用

"""
同步阻塞式调用
"""
import time


def display(num):
    print(num)
    time.sleep(1)


def main():
    for num in range(1, 10):
        display(num)


if __name__ == '__main__':
    main()
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9

异步非阻塞式调用

import asyncio

"""
同步 --- 有序   异步 --- 无序
异步非阻塞式调用  --- > 协作式i并发 --->  多个子程序相互协作提高CPU利用率
"""
import time


async def display(num):
    print(num)
    await asyncio.sleep(1)  # await == yield  主动放弃对CPU的占用,让给其他的



def main():
    cos = [display(num) for num in range(1, 10)]
    # print(cos)  # 列表里是9个协程对象
    loop = asyncio.get_event_loop()
    # asyncio.wait(cos)  # 列表转协程
    loop.run_until_complete(asyncio.wait(cos))  # hello
    loop.close()


if __name__ == '__main__':
    main()
# 3
# 5
# 6
# 1
# 4
# 7
# 8
# 2
# 9

质数迭代器


"""

作业:写一个迭代出从2开始的若干个质数的迭代器
"""


class Prime:

    def __init__(self, num):
        self.a = 1
        self.num = num
        self.counter = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.counter < self.num:
            while True:
                self.a += 1
                flag = 1
                for i in range(2, int(self.a ** 0.5) + 1):
                    if (self.a % i == 0):
                        flag = 0
                        break
                if flag:
                    # print(self.a, '是素数')
                    self.counter += 1
                    return self.a

        raise StopIteration()  # 抛异常来停止迭代


if __name__ == '__main__':
    p1 = Prime(10)
    for x in p1:
        print(x)

异步I/O爬取数据

"""
Time:2021/6/3  16:48
Author:Spectre
"""
"""
如何通过异步I/0爬取数据
aiohttp   /  httpx 

"""

import aiohttp
import asyncio
from bs4 import BeautifulSoup

async def main(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            # print("Status:", response.status)
            # print("Content-type:", response.headers['content-type'])
            html = await response.text()
            bs = BeautifulSoup(html, 'lxml')
            title = bs.select_one('head>title')
            title = title.get_text().replace('\n','') if title else ''
            print(title)

url_list = [
        'https://www.jianshu.com/',
        'https://blog.youkuaiyun.com/',
        'https://www.douyu.com/',
        'https://lol.qq.com/main.shtml',
        'https://www.pinduoduo.com/',
        'https://www.amap.com/',
        'https://www.jd.com/',
        'https://www.taobao.com/',
        'https://www.colorful.cn/',
        'https://www.nvidia.cn/'
]

cos = [main(x) for x in url_list]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(cos))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值