• Python基础


    也是多任务系列哦, 进程, 线程, 微线程, 这样一来, 对于多任务这个话题, 应该算是有所涉猎了吧. 我也不怎用其实.

    微线程, 就是在 单线程的前提下, 完成多任务, 多任务按照一定顺序交替执行. 明明有多线程, 为啥又要提下微线程呢? 这也是因为我用的解释器是 CPython, 它的设计就是单线程的. 即在 CPython 中, 任务都是单线程的, 这是个历史问题, 不想去深究. 总之, 这也是我一旦遇到多任务的场景时, 优先选择多进程, 进程池这些, 比较高效稳定的方式, 尽管其资源开销大 (但机器好呀, 还行吧).

    与其说多任务中, 个人更偏向多进程, 倒不如说, CPython 并未实现多线程, 这也没啥可选的. 而日常任务也更多是单线程的, 如何让单线程跑得更快 那个优先点, 就是咱要谈的 微线程.

    微线程实现 - yield

    关于 yield 在生成器的笔记中已经谈论过了, 着重理解在一个函数中, 跟 return 的区别即可.

    • return 会直接停止掉该函数的运行.
    • 在函数中有 yield 会 反复被执行, 每遇到 yield 就返回值, 直到遇见 return 或 异常则终止.

    详见 生成器: https://www.cnblogs.com/chenjieyouge/p/12285545.html

    def task_01():
        while True:
            print("task_01 is working...")
            yield 666
    
    
    def task_02():
        while True:
            print("task_02 is working...")
            yield 999
    
    
    if __name__ == '__main__':
        t1 = task_01()
        t2 = task_02()
    
        while True:
            next(t1)
            next(t2)
    
    
    task_01 is working...
    task_02 is working...
    task_01 is working...
    task_02 is working...
    task_01 is working...
    task_02 is working...
    ...
    

    这样就简单实现了多任务的不断不断交替执行呀. 但这样我手写的方式, 肯定是不太高效的. Python有相关的第三方库呀, 啥都有, 然后给推荐一波常用的 gevent 贼好用呢.

    gevent - 微线程

    它是对 greenlet 模块的封装, 如果之前有听说过这个包的话. 没有也无所谓, 能学会基本使用就行啦. 它的特点是, 当 greenlet 遇到比较耗时的 IO 操作的时候, 就自动切换到 gevent. 然后等到 IO 操作完成后, 再切回来继续运行.

    IO : 即 input 与 output. 如网络请求任务, 文件读写等这类的比较费时的任务.

    基本使用

    import time
    import gevent
    
    
    def task_01(n):
        for i in range(n):
            print(f"task_01 is working ...{i}")
            
            # 模拟 IO 耗时, 让其自动切换
            gevent.sleep(1)
    
    
    def task_02(n):
        for i in range(n):
            print(f"task_02 is working ...{i}")
    
    
    if __name__ == '__main__':
        g1 = gevent.spawn(task_01, 3)
        g2 = gevent.spawn(task_02, 4)
    
        gevent.joinall([g1, g2])
    
    task_01 is working ...0
    task_02 is working ...0
    task_02 is working ...1
    task_02 is working ...2
    task_02 is working ...3
    task_01 is working ...1
    task_01 is working ...2
    

    在上述代码执行的过程中,我们人为使用 gevent.sleep(0)创建了一个阻塞,gevent在运行到这里时就会自切换函数。也可以在执行的时候sleep更长时间,可以发现两个函数基本是同时运行然后各自等待

    打补丁 - 自动识别 IO

    gevent 里面有个 monkey 模块, 通过 monkey.patch_all () 来自动识别 IO 操作. 咋一听, 自动识别, 听厉害, 但仔细一想, 不就是挨个去判断什么 sys, os, request, htttp ... 这些嘛. 源码大致这样的.

     # 核心的一段哈
        
        # order is important
        if os:
            patch_os()
        if time:
            patch_time()
        if thread:
            patch_thread(Event=Event, _warnings=_warnings)
        # sys must be patched after thread. in other cases threading._shutdown will be
        # initiated to _MainThread with real thread ident
        if sys:
            patch_sys()
        if socket:
            patch_socket(dns=dns, aggressive=aggressive)
        if select:
            patch_select(aggressive=aggressive)
        if ssl:
            patch_ssl(_warnings=_warnings, _first_time=first_time)
        if httplib:
            raise ValueError('gevent.httplib is no longer provided, httplib must be False')
        if subprocess:
            patch_subprocess()
        if builtins:
            patch_builtins()
        if signal:
            patch_signal()
        if queue:
            patch_queue()
    ...
    

    不过还可以了. 如果咱还有其没有覆盖到地方, 那可以去 继承, 然后重写呀. 在刚刚那个例子就可以不用 手动去sleep 因为打了补丁了呀.

    import time
    import gevent
    from gevent import monkey
    
    
    # 给程序打补丁, 让其自动识别 IO 操作
    monkey.patch_all()
    
    def task_01(n):
        for i in range(n):
            print(f"task_01 is working ...{i}")
    
    
    def task_02(n):
        for i in range(n):
            print(f"task_02 is working ...{i}")
    
    
    if __name__ == '__main__':
        g1 = gevent.spawn(task_01, 3)
        g2 = gevent.spawn(task_02, 4)
    
        gevent.joinall([g1, g2])
    
    
    task_01 is working ...0
    task_01 is working ...1
    task_01 is working ...2
    task_02 is working ...0
    task_02 is working ...1
    task_02 is working ...2
    task_02 is working ...3
    
    

    这个补丁 monkey, 确实可以. 我感觉这些老外, 取名字也挺有趣的, 特喜欢用动物的名称.

    栗子 - 小爬虫

    import urllib.request
    import gevent
    from gevent import monkey
    
    monkey.patch_all()
    
    
    def get_image(url_path, file_name):
        try:
            response = urllib.request.urlopen(url_path)
            with open(file_name, "wb") as f:
                while True:
                    image_data = response.read(1024)
                    if image_data:
                        f.write(image_data)
                    else:
                        break
        except:
            print("connect fail")
        else:
            print("success")
    
    
    if __name__ == '__main__':
    
        # 没有真的, 主要是 url 太长了, 影响我排版, 就不搞了.
        image_01 = "https://xxxx.jpg"
        image_02 = "https://xxxxjpg"
        image_03 = "http://www.xxxxp3"
    
        # 创建多个微线程 gevent
        g1 = gevent.spawn(get_image, image_01, "01.jpg")
        g2 = gevent.spawn(get_image, image_02, "02.jpg")
        g3 = gevent.spawn(get_image, image_03, "03.jpg")
    
        gevent.joinall([g1, g2, g3])
    
    

    小结

    • 一个进程, 至少有一个线程; 一个线程里, 可以有多个 微线程
    • 微线程, 是在单线程中的, 可以用 yield 模拟, 推荐用 gevent (别忘 mokey.patch_all) 自动识别 IO
    • 进程是OS 调度的基本单位, 多线程在 CPython 中并未真正实现哦, 故多线程我基本不用
    • 多线程资源开销大, 但我常用, 我就喜欢浪费资源, 你管我呢. 而微线程基于单线程, 开销很小, 我感觉可以多用用

    这样一来, 进程, 线程, 微线程, 一组合起来用, 就蛮有趣的. 我是很少用, 前段时间, 倒是看到我小伙伴用, 多进程 + 微线程, 看他跑任务的时候, 还是蛮酷的哦.

    Python 基础的多任务, 感觉差不多了. 嗯, 基础部分感觉也就这些. 哦, 还差一点 网络编程... 除外也没啥东西了, 编程语言的也基本就这些, 一言隐蔽之.

    变量 -> 表达式 -> 控制流 -> 基础数据结构 -> 函数 -> 面向(过程) 对象 -> IO -> 网络编程 ....

  • 相关阅读:
    wireshark安装
    高效使用搜索引擎
    MFC 网络编程 -- 总结
    MFC Grid control 2.27
    A Popup Progress Window
    Showing progress bar in a status bar pane
    Progress Control with Text
    Report List Controls
    第三方
    CBarChart柱形图类
  • 原文地址:https://www.cnblogs.com/chenjieyouge/p/12438898.html
Copyright © 2020-2023  润新知