{"id":172,"date":"2022-09-25T16:35:00","date_gmt":"2022-09-25T16:35:00","guid":{"rendered":"https:\/\/cloasdata.de\/?p=172"},"modified":"2022-10-15T14:00:05","modified_gmt":"2022-10-15T14:00:05","slug":"horizontal-and-vertical-parallelism","status":"publish","type":"post","link":"https:\/\/cloasdata.de\/?p=172","title":{"rendered":"horizontal and vertical parallelism"},"content":{"rendered":"\n<p>In this post I will try to give you some insights in my mental model about parallelism when using asynchronous or threading style parallelism. And I also show how you can utilize both types simultaneous. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Disclaimer<\/h2>\n\n\n\n<p>This may not be a super valid article about parallelism in Python. It may also not be an proper academic definition of things I write here, but it&#8217;ll address some very basic understanding about how to write proper asynchronous code. <br>You will learn how to run async code at a next level leaving old design patterns from times of threading. <br>I also recommend this read to async beginners to avoid doing things wrong at the first step, because in many cases you will not recognized that there is a better way.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Synchronous: Synchronous but Vertical<\/h2>\n\n\n\n<p>Synchronous programming means that the control flow of the program runs synchronous. Each line of code is executed instruction by instruction and line by line from to bottom allowing only jumps at well defined instructions.  Apart from well defined jumps the code is executed vertically.<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-1 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:500px\">\n<pre title=\"A simple function showing vertical control flow\" class=\"wp-block-code has-small-font-size\"><code lang=\"python\" class=\"language-python\"># synchron code\ndef foo():\n    print(\"starting here\")\n    a = 0\n    a += 1\n    b = (a + 1) \/ 2\n    # this for loop jumps at a well-defined place.\n    for x in range(10):\n        b += x\n    print(\"leaving funtion\")\n    return b\n<\/code><\/pre>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<p>Code goes from top to bottom. <br>Jumps occur here at the for loop<br>Code is executed vertically<\/p>\n<\/div>\n<\/div>\n\n\n\n<p>For sure this nothing new to you but for the next thoughts it could be useful to keep this in mind.<\/p>\n\n\n\n<!--nextpage-->\n\n\n\n<h2 class=\"wp-block-heading\">Threading: Parallel but Horizontal<\/h2>\n\n\n\n<p>Parallel programming or better parallel execution allows code to be run in parallel. So each line can be executed at virtual the same time. Pure time parallelism can only occur when code is executed on more then one processor. If you program is just running on one CPU we are running only virtual parallel and switching from time to time between the tasks.<br>But the programmer cannot determine at which time this exactly happens. Of course there are pretty good scheduler run by the OS and triggered by hardware interrupts so that the timing of context switches is already optimized. But in many cases the programmer knows better in front when there is a good moment to halt and give other code way (for example doing I\/O with the hard-drive). A very interesting stack overflow answer on how and when task switch happen can be found <a href=\"https:\/\/stackoverflow.com\/questions\/12320748\/how-often-per-second-does-windows-do-a-thread-switch\">here<\/a>.<\/p>\n\n\n\n<p><br>My mental model now visualize this as horizontal parallelism because the function, method etc. is executed virtually parallel but in best case at the same line of code.<\/p>\n\n\n\n<pre title=\"A worker function executed on different threads.\" class=\"wp-block-code has-small-font-size\"><code lang=\"python\" class=\"language-python\"># parallel code using threading\ndef bar():\n\n    # this function will be executed virtual parallel. \n    # The context switch and therefore the execution of a line of code \n    # is non-deterministic from perspective of the function.\n    def foo(result):\n        b = 0\n        for x in range(10):\n            b = b + x\n            time.sleep(0.1)  # here we cannot tell that we halt here for some time. \n        result.append(b)\n\n    results = []\n    #  create 2 threads\n    threads = [threading.Thread(target=foo, args=(results,)) for _ in range(2)]\n    # start 2 threads \"parallel\"\n    [t.start() for t in threads]\n    # wait for them\n    [t.join() for t in threads]\n    print(\"result:\", results)\n    # prints result: [45, 45]<\/code><\/pre>\n\n\n\n<p>A typical pattern to use the virtual or actual parallelism is the so called producer\/consumer pattern.<\/p>\n\n\n\n<p>If threads or processes are executing exactly the same code for example inherited from the same function as others do, we call them very often worker. Worker often communicate through a queue where data comes in, is calculated and send out again by another queue. A typical design pattern is the producer\/consumer model. <\/p>\n\n\n\n<pre title=\"Simple producer\/consumer implementation using threading\" class=\"wp-block-code has-small-font-size\"><code lang=\"python\" class=\"language-python\"># this is \"sync\" consumer producer pattern were the parallelism\n# will be added by creating more producer oder consumer\n# instead a queue one also could use a mutable buffer like a list but needs then a semaphore.\n# The queue does this all for us.\ndef producer(q:queue.Queue):\n    for work in range(100):\n        time.sleep(0.001)  # extra heavy work\n        q.put(work)\n    q.put(None)  # poison pill\n    print(\"producer done\")\n\n\ndef consumer(q: queue.Queue):\n    res = 0\n    while True:\n        item = q.get()\n        if item is None:\n            break\n        else:\n            res = res + item\n    print(\"consumer result\", res)\n\n\ndef main():\n    q = queue.Queue()\n    threads = [threading.Thread(target=target, args=(q,)) for target in [producer, consumer]]\n    [t.start() for t in threads]\n    [t.join() for t in threads]\n    print(\"bye\")<\/code><\/pre>\n\n\n\n<p>In traditional multitasking patterns like threading, horizontal parallelism is done at high costs of memory. Threads need their context to run and the context needs to be switched whenever it is time to switch to a different task. In times where memory is cheap and CPU has large bandwidth to the memory this disadvantage tends to disappear as a matter of performance. This also could be a reason why threading is still one of the most popular multitasking paradigm used. But there are many more <a href=\"https:\/\/austingwalters.com\/multithreading-common-pitfalls\/\" data-type=\"URL\" data-id=\"https:\/\/austingwalters.com\/multithreading-common-pitfalls\/\">pitfalls<\/a> one may consider. But that is not subject here. <\/p>\n\n\n\n<!--nextpage-->\n\n\n\n<h2 class=\"wp-block-heading\">Asynchronous: Parallel but Vertical<\/h2>\n\n\n\n<p>In asynchronous or cooperative multitasking the programmer can decide at well defined instruction where code can be executed virtual parallel. <br>This gives availability to make synchronous code vertical parallel. This means that the code itself will be executed in the order of written lines (from top to bottom). But whenever a well defined transition happens other code could be executed in synchronous way at a different place. This is why I call this vertical parallelism.<br>To read such code, you can just ignore the syntax await and you easily understand the execution direction (like synchronous code from top to bottom). <\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code lang=\"python\" class=\"language-python\"># a simple asynchron function will execute vertical line by line \n# allowing at defined places to halt(wait) and resume for\n# other code to be executed\nasync def foo():\n    print(\"starting here\")\n    a = 1\n    await asyncio.sleep(1)  # at this place other code can be executed\n    print(\"slept for 1 second\")\n    a += 2\n    await asyncio.sleep(0.5)  # at this place other code can be executed\n    print(\"leaving\")\n    return a<\/code><\/pre>\n\n\n\n<p>The above code does exact same as the below sync version of it. <\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code lang=\"python\" class=\"language-python\">async def foo():\n    print(\"starting here\")\n    a = 1\n    time.sleep(1)  # at this place other code can be executed\n    print(\"slept for 1 second\")\n    a += 2\n    time.sleep(0.5)  # at this place other code can be executed\n    print(\"leaving\")\n    return a<\/code><\/pre>\n\n\n\n<p>In the sync case the interpreter is deemed to wait for the sleep time. In background they so called global interpreter lock (GIL) is released and the OS can continue switch to another thread. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Parallel but horizontal and vertical<\/h2>\n\n\n\n<p>Now combining both direction could give us a very high performant pattern. The basic asynchronous framework, asyncio, in Python has therefore a simple wrapper for coroutines. Asyncios event loop comes with a method called create_task. This method wraps the passed coroutine into a task object (a subclass of asyncios Future). The task execution is then scheduled by the event loop. The programmer should hold reference because asyncio\/python will garbage collect after the coroutine has finished. <\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code lang=\"python\" class=\"language-python\">async def request(page:int,  client: aiohttp.ClientSession):\n        response = await client.get(f\"https:\/\/books.toscrape.com\/catalogue\/page-{page}.html\")\n        if response.status &lt; 300:\n            body = await response.text()\n            print(body[:18].strip())\n\n\nasync def main():\n    client = aiohttp.ClientSession()\n    loop = asyncio.get_event_loop()\n    time_taken = time.time()\n    async with client:\n        # hold reference here\n        pending = [loop.create_task(request(page, client)) for page in range(10)]\n        # wait for them and release when all complete (default of wait())\n        done, pending = await asyncio.wait(pending, return_when=asyncio.ALL_COMPLETED)\n    print(f\"done in {time.time() - time_taken:.2f}\")\n    await client.close()\n    await asyncio.sleep(0.5)\n\nif __name__ == '__main__':\n    asyncio.run(main(), debug=True)\n    # prints \n    # &lt;!DOCTYPE html&gt;\n    # ...\n    # done in 1.02<\/code><\/pre>\n\n\n\n<p>As you can see the request coroutine will first issue a http request and wait for it. <br>When the we received a response, we will check its status and then wait again to read the buffer from network interface. This so far is vertical and typical for asynchronous style.<br>But this is not enough, so we issue 10 request coroutines at nearly the same time, doing all the above stuff, but 10 times horizontal parallel. <\/p>\n\n\n\n<p>To control the way how asyncio waits for the task a fine grained  condition can be passed. I recommend to read <a href=\"https:\/\/docs.python.org\/3\/library\/asyncio-task.html#asyncio.wait\">asyncios docs<\/a>. <br>Below you can find low performance version. At least it is non blocking, but it is still as slow as a synchronous version would be, because in the list comprehension we wait for each request to finish and than issue a new one. In conclusion even as the underlying request coroutine is asynchronous you can do things easily in a wrong way, by synchronizing things carelessly. <\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code lang=\"python\" class=\"language-python\">async def request(page: int, client: aiohttp.ClientSession):\n    response = await client.get(f\"https:\/\/books.toscrape.com\/catalogue\/page-{page}.html\")\n    if response.status &lt; 300:\n        body = await response.text()\n        print(body[:18].strip())\n\n\nasync def main():\n    client = aiohttp.ClientSession()\n    loop = asyncio.get_event_loop()\n    time_taken = time.time()\n    async with client:\n        results = [await request(page, client) for page in range(10)]\n    print(f\"done in {time.time() - time_taken:.2f}\")\n    await client.close()\n    await asyncio.sleep(0.5)\n\n\nif __name__ == '__main__':\n    asyncio.run(main(), debug=True)\n    # prints\n    # &lt;!DOCTYPE html&gt;\n    # ...\n    # done in 2.50<\/code><\/pre>\n\n\n\n<p>At the end of this blog I really encourage you to read the docs of asyncio and read source code or buy a book.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this post I will try to give you some insights in my mental model about parallelism when using asynchronous or threading style parallelism. And I also show how you can utilize both types simultaneous. Disclaimer This may not be a super valid article about parallelism in Python. It may also not be an proper [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":284,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[4],"tags":[6,12,5],"class_list":["post-172","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog","tag-asyncio","tag-parallelism","tag-python"],"_links":{"self":[{"href":"https:\/\/cloasdata.de\/index.php?rest_route=\/wp\/v2\/posts\/172","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloasdata.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloasdata.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloasdata.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cloasdata.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=172"}],"version-history":[{"count":27,"href":"https:\/\/cloasdata.de\/index.php?rest_route=\/wp\/v2\/posts\/172\/revisions"}],"predecessor-version":[{"id":341,"href":"https:\/\/cloasdata.de\/index.php?rest_route=\/wp\/v2\/posts\/172\/revisions\/341"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloasdata.de\/index.php?rest_route=\/wp\/v2\/media\/284"}],"wp:attachment":[{"href":"https:\/\/cloasdata.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=172"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloasdata.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=172"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloasdata.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=172"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}