Python异步并发处理别样视角

背景知识视频教程

  • Python并行编程解决方案
  • 适用于分布式任务和并行编程的Python Celery
  • 使用Python并行和并发编程

异步编程是一种并行编程,其中一个工作单元被允许与主应用程序线程分开运行。 工作完成后,它将通知主线程有关工作线程的完成或失败。 使用它有很多好处,例如提高了应用程序性能和增强了响应能力。

异步性似乎是Node.js在服务器端编程中如此受欢迎的一个重要原因。 我们编写的大部分代码(尤其是在网站等大型IO应用程序中)都依赖于外部资源。 从远程数据库调用到POST到REST服务,这可以是任何东西。 一旦您请求这些资源中的任何一个,您的代码就无所事事。 使用异步编程,您可以让代码在等待其他资源响应的同时处理其他任务。

多项任务处理

多个进程

最明显的方法是使用多个进程。 在终端上,您可以启动脚本两次,三遍,四遍…十遍,然后所有脚本将独立运行或同时运行。 底层的操作系统将负责在所有这些实例之间共享您的CPU资源。 或者,您可以使用支持生成程序的多处理库,如下例所示。

from multiprocessing import Process


def print_func(continent='Asia'):
    print('The name of continent is : ', continent)

if __name__ == "__main__":  # confirms that the code is under main function
    names = ['America', 'Europe', 'Africa']
    procs = []
    proc = Process(target=print_func)  # instantiating without any argument
    procs.append(proc)
    proc.start()

    # instantiating process with arguments
    for name in names:
        # print(name)
        proc = Process(target=print_func, args=(name,))
        procs.append(proc)
        proc.start()

    # complete the processes
    for proc in procs:
        proc.join()

输出

The name of continent is :  Asia
The name of continent is :  America
The name of continent is :  Europe
The name of continent is :  Africa

多线程

同时运行多件事情的下一种方法是使用线程。 线程是一条执行线,非常类似于一个进程,但是您可以在一个进程的上下文中拥有多个线程,并且它们都共享对公共资源的访问。 但是正因为如此,很难编写线程代码。 再一次,操作系统在共享CPU方面做了所有繁重的工作,但是全局解释器锁(GIL)允许一个线程在给定时间仅运行Python代码,即使您有多个线程在运行代码。 因此,在CPython中,GIL可防止多核并发。 基本上,即使您有两个或四个或更多,您也可以在单个内核中运行。

import threading
 
def print_cube(num):
    """
    function to print cube of given num
    """
    print("Cube: {}".format(num * num * num))
 
def print_square(num):
    """
    function to print square of given num
    """
    print("Square: {}".format(num * num))
 
if __name__ == "__main__":
    # creating thread
    t1 = threading.Thread(target=print_square, args=(10,))
    t2 = threading.Thread(target=print_cube, args=(10,))
 
    # starting thread 1
    t1.start()
    # starting thread 2
    t2.start()
 
    # wait until thread 1 is completely executed
    t1.join()
    # wait until thread 2 is completely executed
    t2.join()
 
    # both threads completely executed
    print("Done!")

输出

Square: 100
Cube: 1000
Done!

使用yield的协程

协程是子例程的泛化。 它们用于协作式多任务处理,在协作式多任务处理中,一个进程会定期或在空闲时自动产生(放弃)控制,以使多个应用程序可以同时运行。 协程与生成器类似,但协程很少,并且我们使用yield语句的方式略有变化。 生成器产生用于迭代的数据,而协程也可以消耗数据。

def print_name(prefix):
    print("Searching prefix:{}".format(prefix))
    try : 
        while True:
                # yeild used to create coroutine
                name = (yield)
                if prefix in name:
                    print(name)
    except GeneratorExit:
            print("Closing coroutine!!")
 
corou = print_name("Dear")
corou.__next__()
corou.send("James")
corou.send("Dear James")
corou.close()

输出

Searching prefix:Dear
Dear James
Closing coroutine!!

异步

第四种方式是异步编程,其中OS不参与其中。 就操作系统而言,您将只有一个进程,并且在该进程中将只有一个线程,但是您将能够一次执行多项操作。 那么,诀窍是什么?

答案是asyncio模块,它旨在使用协程和futures来简化异步代码,并使其几乎与同步代码一样可读,因为没有回调。

接下来,我们运行3个异步任务,分别查询Reddit,提取并打印JSON。我们利用aiohttp这个http客户端库来确保HTTP请求也异步运行。

import signal  
import sys  
import asyncio  
import aiohttp  
import json

loop = asyncio.get_event_loop()  
client = aiohttp.ClientSession(loop=loop)

async def get_json(client, url):  
    async with client.get(url) as response:
        assert response.status == 200
        return await response.read()

async def get_reddit_top(subreddit, client):  
    data1 = await get_json(client, 'https://www.reddit.com/r/' + subreddit + '/top.json?sort=top&t=day&limit=5')

    j = json.loads(data1.decode('utf-8'))
    for i in j['data']['children']:
        score = i['data']['score']
        title = i['data']['title']
        link = i['data']['url']
        print(str(score) + ': ' + title + ' (' + link + ')')

    print('DONE:', subreddit + '\n')

def signal_handler(signal, frame):  
    loop.stop()
    client.close()
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)

asyncio.ensure_future(get_reddit_top('python', client))  
asyncio.ensure_future(get_reddit_top('programming', client))  
asyncio.ensure_future(get_reddit_top('compsci', client))  
loop.run_forever()

输出

50: Undershoot: Parsing theory in 1965 (http://jeffreykegler.github.io/Ocean-of-Awareness-blog/individual/2018/07/knuth_1965_2.html)
12: Question about best-prefix/failure function/primal match table in kmp algorithm (https://www.reddit.com/r/compsci/comments/8xd3m2/question_about_bestprefixfailure_functionprimal/)
1: Question regarding calculating the probability of failure of a RAID system (https://www.reddit.com/r/compsci/comments/8xbkk2/question_regarding_calculating_the_probability_of/)
DONE: compsci

336: /r/thanosdidnothingwrong -- banning people with python (https://clips.twitch.tv/AstutePluckyCocoaLitty)
175: PythonRobotics: Python sample codes for robotics algorithms (https://atsushisakai.github.io/PythonRobotics/)
23: Python and Flask Tutorial in VS Code (https://code.visualstudio.com/docs/python/tutorial-flask)
17: Started a new blog on Celery - what would you like to read about? (https://www.python-celery.com)
14: A Simple Anomaly Detection Algorithm in Python (https://medium.com/@mathmare_/pyng-a-simple-anomaly-detection-algorithm-2f355d7dc054)
DONE: python

1360: git bundle (https://dev.to/gabeguz/git-bundle-2l5o)
1191: Which hashing algorithm is best for uniqueness and speed? Ian Boyd's answer (top voted) is one of the best comments I've seen on Stackexchange. (https://softwareengineering.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and-speed)
430: ARM launches "Facts" campaign against RISC-V (https://riscv-basics.com/)
244: Choice of search engine on Android nuked by "Anonymous Coward" (2009) (https://android.googlesource.com/platform/packages/apps/GlobalSearch/+/592150ac00086400415afe936d96f04d3be3ba0c)
209: Exploiting freely accessible WhatsApp data or "Why does WhatsApp web know my phone's battery level?" (https://medium.com/@juan_cortes/exploiting-freely-accessible-whatsapp-data-or-why-does-whatsapp-know-my-battery-level-ddac224041b4)
DONE: programming

此外,在某些情况下,您可能需要在不同服务器之间分配任务。 在这种情况下,我们可以利用RQ(Redis Queue)。 这是一个简单的Python库,用于对作业进行排队并与工作人员在后台进行处理。 它由Redis(键/值数据存储)支持。

在下面的示例中,我们使用Redis将简单的函数count_words_at_url排队。

from mymodule import count_words_at_url
from redis import Redis
from rq import Queue


q = Queue(connection=Redis())
job = q.enqueue(count_words_at_url, 'http://nvie.com')


******mymodule.py******

import requests

def count_words_at_url(url):
    """Just an example function that's called async."""
    resp = requests.get(url)

    print( len(resp.text.split()))
    return( len(resp.text.split()))

输出

15:10:45 RQ worker 'rq:worker:EMPID18030.9865' started, version 0.11.0
15:10:45 *** Listening on default...
15:10:45 Cleaning registries for queue: default
15:10:50 default: mymodule.count_words_at_url('http://nvie.com') (a2b7451e-731f-4f31-9232-2b7e3549051f)
322
15:10:51 default: Job OK (a2b7451e-731f-4f31-9232-2b7e3549051f)
15:10:51 Result is kept for 500 seconds

另一种异步库

另一种异步的HTTP客户端请求

并发库

异步并发的基本思想

并发性有限的大量任务

发出1亿个请求

添加并发限制

程序员的AsyncIO

Asyncio协程模式

Celery的异步任务

异步微服务

详情参阅http://viadean.com/py_async.html

你可能感兴趣的:(Python)