Full download Using Asyncio in Python 3 Caleb Hattingh pdf docx
Full download Using Asyncio in Python 3 Caleb Hattingh pdf docx
com
https://textbookfull.com/product/using-asyncio-in-
python-3-caleb-hattingh/
OR CLICK BUTTON
DOWNLOAD NOW
https://textbookfull.com/product/using-asyncio-in-python-
understanding-python-s-asynchronous-programming-features-caleb-
hattingh/
textboxfull.com
https://textbookfull.com/product/practical-programming-an-
introduction-to-computer-science-using-python-3-6-3rd-edition-paul-
gries/
textboxfull.com
https://textbookfull.com/product/python-basics-a-practical-
introduction-to-python-3-fletcher-heisler/
textboxfull.com
https://textbookfull.com/product/supervised-learning-with-python-
concepts-and-practical-implementation-using-python-vaibhav-verdhan/
textboxfull.com
Python 2 and 3 Compatibility: With Six and Python-Future
Libraries Nanjekye
https://textbookfull.com/product/python-2-and-3-compatibility-with-
six-and-python-future-libraries-nanjekye/
textboxfull.com
https://textbookfull.com/product/advanced-guide-to-
python-3-programming-hunt/
textboxfull.com
https://textbookfull.com/product/python-programming-using-problem-
solving-approach-thareja-reema/
textboxfull.com
https://textbookfull.com/product/supervised-learning-with-python-
concepts-and-practical-implementation-using-python-1st-edition-
vaibhav-verdhan/
textboxfull.com
Topics
Copyright © 2018 O’Reilly Media, Inc. All rights reserved.
Tutorials
Printed in the United States of America.
Highlights
O’Reilly books may be purchased for educational, business, or sales promotional use. Online
editions are also available for most titles (http://oreilly.com/safari). For more information,
Settings
contact our corporate/institutional sales department: 8009989938 or [email protected].
Support
Editors: Jeff Bleiel and Susan Conant
Sign Out
Production Editor: Nicholas Adams
The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. Using Asyncio in Python 3,
the cover image, and related trade dress are trademarks of O’Reilly Media, Inc.
While the publisher and the author have used good faith efforts to ensure that the information
and instructions contained in this work are accurate, the publisher and the author disclaim all
responsibility for errors or omissions, including without limitation responsibility for damages
resulting from the use of or reliance on this work. Use of the information and instructions
contained in this work is at your own risk. If any code samples or other technology this work
contains or describes is subject to open source licenses or the intellectual property rights of
others, it is your responsibility to ensure that your use thereof complies with such licenses
and/or rights.
9781491999684
[LSI]
y
Chapter 1. Introduction
History
Topics
My story is a lot like yours, only more interesting ’cause it involves robots.
—Bender, Futurama episode “30% Iron Chef”
Tutorials
The most common question I receive about Asyncio in Python 3 is this: “What is it, and what do
I do
Offers with
& Deals it?” The following story provides a backdrop for answering these questions. The
central focus of Asyncio is about how to best perform multiple concurrent tasks at the same
Highlights
time. And not just any sort of tasks, but specifically tasks that involve waiting periods. The key
insight required with this style of programming is that while you wait for this task to complete,
Settings
work on other tasks can be performed.
Support
The Restaurant of ThreadBots
Sign Out
The year is 2051, and you find yourself in the restaurant business! Automation, largely by robot
workers, powers most of the economy, but it turns out that humans still enjoy going out to eat
once in a while. In your restaurant, all the employees are robots; humanoid, of course, but
unmistakably robots. The most successful manufacturer of robots is of course Threading Inc.,
and robot workers from this company have come to be called “ThreadBots.”
Except for this small robotic detail, your restaurant looks and operates like one of those oldtime
restaurants from, say, 2018. Your guests will be looking for that vintage experience. They want
fresh food prepared from scratch. They want to sit at tables. They want to wait for their meals—
but only a little. They want to pay at the end, and they sometimes even want to leave a tip, for
oldtime’s sake, of course.
Naturally, being new to the robotic restaurant business, you do what every other restaurateur
does, and you hire a small fleet of robots: one to greet new diners at the front desk (hostbot);
one to wait tables and take orders (waitbot); one to do the cooking (chefbot); and one to manage
the bar (winebot).
Hungry diners will arrive at the front desk, and will be greeted by your frontofhouse threadbot.
They are then directed to a table, and once they are seated, your waiter threadbot will take their
order. Then, the waiter threadbot will take that order to the kitchen on a slip of paper (because
you want to preserve that oldtime experience, remember?). The chefbot will look up the order
on the slip and begin preparing the food. The waitbot will periodically check whether the food is
ready, and if so, will immediately take the dish to the customer’s table. When the guests are
ready to leave, they return to greetbot who calculates the bill, and graciously wishes them a
pleasant evening further.
You soon open your restaurant, and exactly as you had anticipated, your menu is a hit and you
soon grow a large customer base. Your robot employees do exactly what they’re told, and they
are perfectly good at the tasks you assigned them. Everything is going really well, and you
really couldn’t be happier.
Over time, however, you do begin to notice some problems. Oh, it’s nothing truly serious. Just a
few things that seem to go wrong. Every other robotic restaurant owner seems to have similar
niggling problems. It is a little worrying that these problems seem to get worse the more
successful you become.
Though rare, there are the occasional collisions that are very unsettling: sometimes, when a
plate of food is ready in the kitchen, the waitbot will grab it before the chefbot has even let go of
the plate! This usually shatters the plate and leaves a big mess. Chefbot cleans it up of course,
but still, you’d think that these topnotch robots would know how to be a bit more synchronized
with each other. This happens at the bar too: sometimes winebot will place a new drink order on
the bar and waitbot will grab it before winebot has let go, resulting in broken glass and spilled
Nederburg Cabernet Sauvignon!
Sometimes greetbot will seat new diners at exactly the same moment that waitbot has decided to
clean what it thinks was an empty table. It’s pretty awkward for the diners! You’ve tried adding
delay logic to the waitbot’s cleaning function, or delays to the greetbot’s seating function, but
these don’t really help, because the collisions still occur. At least these are only rare events.
Well, these used to be rare events. Your restaurant got so popular that you’ve had to hire a few
more threadbots. For very busy Friday and Saturday evenings, you’ve had to add a second
greetbot and two extra waitbots. Unfortunately the hiring contracts for threadbots mean that you
have to hire for a whole week, so this effectively means that for most of the quiet part of the
week, you’re carrying three extra threadbots that you don’t really need.
The other resource problem, in addition to the extra cost, of course, is that it’s more work for
you to deal with these extra threadbots. It was fine to keep tabs on just four bots, but now you’re
up to seven! Keeping track of seven threadbots is a lot more work, and because your restaurant
keeps getting more and more famous, you become worried about taking on even more
threadbots. It’s going to become a fulltime job just to keep track of what each threadbot is
doing! Another thing: these extra theadbots are using up a lot more space inside your restaurant.
It’s becoming a tight squeeze for your customers, what with all these robots zipping around.
You’re worried that if you need to add even more bots, this space problem is going to get even
worse. You want to use the space in your restaurant for customers, not threadbots!
The collisions have also become worse since you added more threadbots. Now, sometimes two
waitbots take the exact same order from the same table at the same time. It’s as if they both
noticed that the table was ready to order and moved in to take it, without noticing that the other
waitbot was doing the exact same thing. As you can imagine, this results in duplicated food
orders which causes extra load on the kitchen and increases the chance of collisions when
picking up the ready plates. You’re worried that if you added more waitbots, this problem might
get worse.
Time passes.
Then, during one very, very busy Friday night service, you have a singular moment of clarity:
time slows, lucidity overwhelms you and you see a snapshot of your restaurant frozen in time.
My threadbots are doing nothing! Not really nothing, to be fair, but they’re just…waiting.
Each of your three waitbots at different tables is waiting for one of the diners at their table to
give their order. The winebot already prepared 17 drinks which are now waiting to be collected
(it took only a few seconds), and is now waiting for a new drink order. One of the hostbots has
greeted a new party of guests, told them they need to wait a minute to be seated, and is waiting
for the guest to respond. The other hostbot, now processing a credit card payment for another
guest that is leaving, is waiting for confirmation on the payment gateway device. Even the
chefbot, who is currently cooking 35 meals, is not actually doing anything at this moment, but is
simply waiting for one of the meals to complete cooking so that it can be plated up and handed
over to a waitbot.
You realize that even though your restaurant is now full of threadbots, and you’re even
considering getting more (with all the problems that entails), the ones that you currently have
are not even being fully utilized.
The moment passes, but not the realization. You wait for weekend service to pass, and the first
thing you do is add a data collection module to your threadbots. For each threadbot, you’re
measuring how much time is spent waiting and how much is spent actively doing work. Over
the course of the following week, the data is collected and then on Sunday evening you analyze
the results. It turns out that even when your restaurant is at full capacity, the most hardworking
threadbot is idle for about 98% of the time! The threadbots are so enormously efficient that they
can perform any task in fractions of a second.
As an entrepreneur, this inefficiency really bugs you. You know that every other robotic
restaurant owner is running their business the same as you, with many of the same problems.
But, you think, slamming your fist on your desk, “There must be a better way!”
So the very next day, which is a quiet Monday, you try something very bold: you program a
single threadbot to do all the tasks. But every time it begins to wait, even for a second, instead
of waiting, the threadbot will switch to the next task, whatever it may be in the entire restaurant.
It sounds incredible at face value, only one threadbot doing the work of all the others, but you’re
confident that your calculations are correct. And besides, Monday is a very quiet day; so even if
something goes wrong, the impact will be small. For this new project, you call the bot “loopbot”
because it will loop over all the jobs in the restaurant.
The programming was more difficult than usual. It isn’t just that you had to program one
threadbot with all the different tasks; you also had to program some of the logic of when to
switch between tasks. But by this stage, you’ve had a lot of experience with programming these
threadbots so you manage to get it done.
Monday arrives, and you watch your loopbot like a hawk. It moves between stations in fractions
of a second, checking whether there is work to be done. Not long after opening, the first guest
arrives at the front desk. The loopbot shows up almost immediately, and asks whether the guest
would like a table near the window or near the bar. And then, as the loopbot begins to wait, its
programming tells it to switch to the next task, and it whizzes off! This seems like a dreadful
error, but then you see that as the guest begins to say “window please,” the loopbot is back! It
receives the answer and directs the guest to table 42. And off it goes again, checking for drinks
orders, food orders, table cleanup, and arriving guests, over and over again.
Late Monday evening, you congratulate yourself on a remarkable success! You check the data
collection module on the loopbot, and it confirms that even with a single threadbot doing the
work of seven, the idle time was still around 97%! This result gives you the confidence to
continue the experiment all through the rest of the week.
As the busy Friday service approaches, you reflect on the great success of your experiment. For
service during a normal working week, you can easily manage the workload with a single
loopbot. And there is another thing you’ve noticed: you don’t see any more collisions. It makes
sense: since there is only one loopbot, it cannot get confused with itself. No more duplicate
orders going to the kitchen, and no more confusion about when to grab a plate or drink.
Friday evening service begins, and as you had hoped, the single threadbot keeps up with all the
customers and tasks, and service is proceeding even better than before. You imagine that you
can take on even more customers now, and you don’t have to worry about having to bring on
more threadbots. You think of all the money you’re going to save.
Unfortunately, something goes wrong: one of the meals, an intricate souffle, has flopped! This
has never happened before in your restaurant. You begin to study the loopbot more closely. It
turns out that at one of your tables, there is a very chatty guest. This guest has come to your
restaurant alone, and keeps trying to make conversation with your loopbot, even sometimes
holding your loopbot by the hand. When this happens, your loopbot is unable to dash off and
attend to the evergrowing list of tasks elsewhere in your restaurant. This is why the kitchen
produced its first flopped souffle. Your loopbot was unable to make it back to the kitchen to
remove a souffle from the oven, because it was held up by a guest.
Friday service finishes, and you head home to reflect on what you have learned. It’s true that the
loopbot could still do all the work that was required on a busy Friday service; but on the other
hand, your kitchen produced its very first spoiled meal, something that has never happened
before. Chatty guests used to keep waitbots busy all the time, but that never affected the kitchen
service at all.
All things considered, you ponder, it is still better to continue using a single loopbot. Those
worrying collisions no longer occur, and there is much more space in your restaurant, space that
you can use for more customers. But you realize something profound about the loopbot: it can
only be effective if every task is short; or at least can be performed in a very short period of
time. If any activity keeps the loopbot busy for too long, other tasks will begin to suffer neglect.
It is difficult to know in advance which tasks may take too much time. What if a guest orders a
cocktail that requires very intricate preparation, much more than usual? What if a guest wants to
complain about a meal at the frontdesk, refuses to pay, and grabs the loopbot by the arm,
preventing it from taskswitching? You decide that instead of figuring out all of these issues up
front, it is better to continue with the loopbot, record as much information as possible, and deal
with any problems later as they arise.
Gradually, other restaurant owners notice your operation, and eventually they figure out that
they too can get by, and even thrive, with only a single threadbot. Word spreads. Soon every
single restaurant operates in this way, and it becomes difficult to remember that robotic
restaurants ever operated with multiple threadbots at all.
Epilogue
In our story, each of the robot workers in the restaurant is a single thread. The key observation
in the story is that the nature of the work in our restaurant involves a great deal of waiting, just
as requests.get() is waiting for a response from a server.
In a restaurant, the worker time spent waiting isn’t huge when slow humans are doing manual
work, but when superefficient and quick robots are doing the work, then nearly all their time is
spent waiting. With computer programming, the same is true when network programming is
involved. CPUs do “work” and “wait” on network I/O. CPUs in modern computers are
extremely fast, hundreds of thousands of times faster than network traffic. Thus, CPUs running
networking programs spend a great deal of time waiting.
The insight in the story is that programs can be written to explicitly direct the CPU to move
between work tasks as necessary. While there is an improvement in economy (using fewer
CPUs for the same work), the real advantage, compared to a threading (multiCPU) approach is
the elimination of race conditions.
It’s not all roses, however: as we found in the story, there are benefits and drawbacks to most
technology solutions. The introduction of the LoopBot solved a certain class of problems, but
also introduced new problems—not least of which is that the restaurant owner had to learn a
slightly different way of programming.
Asyncio offers a safer alternative to preemptive multitasking (i.e., using threads), thereby
avoiding the bugs, race conditions, and other nondeterministic dangers that frequently
occur in nontrivial threaded applications.
That’s it.
Network programming is not one of those domains. The key insight is that network
programming involves a great deal of “waiting for things to happen,” and because of this, we
don’t need the operating system to efficiently distribute our tasks over multiple CPUs.
Furthermore, we don’t need the risks that preemptive multitasking brings, such as race
conditions when working with shared memory.
However, there is a great deal of misinformation about other supposed benefits of eventbased
programming models that just ain’t so. Here are a few:
Unfortunately, no. In fact, most benchmarks seem to show that threading solutions are
slightly faster than their comparable Asyncio solutions. If the extent of concurrency itself is
considered a performance metric, Asyncio does make it a bit easier to create very large
numbers of concurrent socket connections though. Operating systems often have limits on
how many threads can be created, and this number is significantly lower than the number of
socket connections that can be made. The OS limits can be changed, but it is certainly easier
to do with Asyncio. And while we expect that having many thousands of threads should
incur extra contextswitching costs that coroutines avoid, it turns out to be difficult to
benchmark this in practice.1 No, speed is not the benefit of Asyncio in Python; if that’s
what you’re after, try Cython instead!
Definitely not! The true value of threading lies in being able to write multiCPU programs,
in which different computational tasks can share memory. The numerical library numpy, for
instance, already makes use of this by speeding up certain matrix calculations through the
use of multiple CPUs, even though all the memory is shared. For sheer performance, there is
no competitor to this programming model for CPUbound computation.
Again, no. It is true that Asyncio is not affected by the GIL,2 but this is only because the
GIL affects multithreaded programs. The “problems” with the GIL that people refer to are
that it prevents true multicore concurrency when using threads. Since Asyncio is single
threaded (almost by definition), it is unaffected by the GIL, but it also cannot benefit from
multiple CPU cores either.3 It is also worth pointing out that in multithreaded code, the
Python GIL can cause additional performance problems beyond what has already been
mentioned in other points: Dave Beazley presented a talk, “Understanding the Python GIL,”
at PyCon 2010, and much of what is discussed in that talk remains true today.
Asyncio prevents all race conditions
False. The possibility of race conditions is always present with any concurrent
programming, regardless of whether threading or eventbased programming is used. It is
true that Asyncio can virtually eliminate a certain class of race conditions common in
multithreaded programs, such as intraprocess shared memory access. However, it doesn’t
eliminate the possibility of other kinds of race conditions, for example interprocess races
with shared resources common in distributed microservices architectures. You must still pay
attention to how shared resources are being used. The main advantage of Asyncio over
threaded code is that the points at which control of execution is transferred between
coroutines are visible (due to the presence of await keywords), and thus it is much easier
to reason about how shared resources are being accessed.
The last myth is the most dangerous one. Dealing with concurrency is always complex,
regardless of whether you’re using threading or Asyncio. When experts say, “Asyncio makes
concurrency easier,” what they really mean is that Asyncio makes it a little easier to avoid
certain kinds of truly nightmarish racecondition bugs; the kind that keep you up at night, and
about which you tell other programmers in hushed tones over campfires, wolves howling in the
distance.
Even with Asyncio, there is still a great deal of complexity to deal with. How will your
application support health checks? How will you communicate with a database which may
allow only a few connections, much fewer than your 5,000 socket connections to clients? How
will your program terminate connections gracefully when you receive a signal to shut down?
How will you handle (blocking!) disk access and logging? These are just a few of the many
complex design decisions that you will have to answer.
Application design will still be difficult, but the hope is that you will have an easier time
reasoning about your application logic when you have only one thread to deal with.
1 Research in this area seems hard to find, but the numbers seem to be around 50
microseconds per threaded contextswitch on Linux on modern hardware. To give a (very)
rough idea: a thousand threads implies 50 ms total cost just for the context switching. It
does add up, but it isn’t going to wreck your application either.
2 The global interpreter lock (GIL) makes the Python interpreter code (not your code!)
threadsafe by locking the processing of each opcode; it has the unfortunate side effect of
effectively pinning the execution of the interpreter to a single CPU, and thus preventing
multicore parallelism.
3 This is similar to how JavaScript lacks a GIL “problem”: there is only one thread.
Chapter 2. The Truth About Threads
History
Topics
Let’s be frank for a moment—you really don’t want to use Curio. All things equal, you should
probably be programming with threads. Yes, threads. THOSE threads. Seriously. I’m not
Tutorials
kidding.1
—Dave Beazley, Developing with Curio
Offers & Deals
This is the part where I tell you, “Threads are terrible and you should never use them,” right?
Unfortunately,
Highlights the situation is not so simple. We need to weigh the benefits and risks with using
threads, just like any technology choice.
Settings
This book is not supposed to be about threads at all. But the problem is that (a) Asyncio is
offered
Support as an alternative to threading, so it’s hard to understand the value proposition without
some comparison; and (b), even when using Asyncio you will still likely have to deal with
Sign Out
threads and processes, so you still need to know something about threading regardless.
IMPORTANT
Benefits of Threading
These are the main benefits of threading:
Your code can run concurrently but still be set out in a very simple, topdown linear
sequence of commands to the point where—and this is key—you can pretend within the
body of your functions, that no concurrency is happening.
Parallelism with shared memory
Your code can exploit multiple CPUs while still having threads share memory. This is
important in many workloads where it would be too costly to move large amounts of data
between the separate memory spaces of different processes, for example.
There is a large body of knowledge and best practice for writing threaded applications.
There is also a huge amount of existing “blocking” code that depends on multithreading for
concurrent operation.
Now, with Python, the point about parallelism is questionable because the Python interpreter
uses a global lock, called the Global Interpreter Lock, to protect the internal state of the
interpreter itself—protection from the potential catastrophic effects of race conditions between
multiple threads. A side effect of the lock is that it ends up pinning all threads in your program
to a single CPU. As you might imagine, this negates any parallelism performance benefits
(unless you use tools like Cython or Numba to maneuver around the limitation).
The first point regarding perceived simplicity, however, is significant: threading in Python feels
exceptionally simple, and if you haven’t been burned before by impossibly hard racecondition
bugs, threading offers a very attractive concurrency model. And if you have been burned in the
past, threading remains a compelling option because you will likely have learned (the hard way)
how to keep the code both simple and safe.
I don’t have space to get into safer threaded programming here, but generally speaking, the best
practice for using threads is to use the ThreadPoolExecutor() class from the
concurrent.future module, passing all required data in through the submit() method.
Here is a basic example:
def worker(data):
<process the data>
In general you would prefer that your tasks are somewhat shortlived, so that when your
program needs to shut down, you can simply call Executor.shutdown(wait=True) and
wait a second or two to allow the executor to complete.
Most important: if at all possible, try to prevent your threaded code (in the example above, the
worker() function) from accessing or writing to any global variables!
Several great guidelines for safer threaded code were presented by Raymond Hettinger at PyCon
Russia 2016 and again at PyBay 2017 and I strongly urge you to add these videos to your watch
list.
Drawbacks of Threading
…nontrivial multithreaded programs are incomprehensible to humans. It is true that the
programming model can be improved through the use of design patterns, better granularity of
atomicity (e.g. transactions), improved languages, and formal methods. However, these
techniques merely chip away at the unnecessarily enormous nondeterminism of the threading
model. The model remains intrinsically intractable.2
—Edward A Lee, The Problem with Threads Technical Report No.
UCB/EECS20061 Electrical Engineering and Computer Sciences
University of California at Berkeley
These are not new, and have been mentioned in several other places already, but for
completeness let’s collect them here anyway:
Threading bugs and race conditions in threaded programs are the hardest kinds of bugs to
fix. With experience, it is possible to design new software that is less prone to these
problems, but in nontrivial, naively designed software they can be nearly impossible to
fix, even by experts! Really!
# threadmem.py
import os
from time import sleep
from threading import Thread
threads = [
Thread(target=lambda: sleep(60)) for i in range(10000)
]
[t.start() for t in threads]
print(f'PID = {os.getpid()}')
[t.join() for t in threads]
At very high concurrency levels (say, >5,000 threads), there can also be an impact on
throughput due to contextswitching costs,4 ,5 assuming you can figure out how to
configure your operating system to even allow you to create that many threads! It has
become so tedious now on recent macOS versions, for example, to test my 10,000 do
nothingthreads example above, that I gave up trying to raise the limits at all.
Threading is inflexible: the operating system will continually share CPU time with all
threads regardless of whether a thread is ready to do work or not. For instance, a thread
may be waiting for data on a socket, but the OS scheduler may still switch to and from that
thread thousands of times before any actual work needs to be done. (In the async world,
the select() system call is used to check whether a socketawaiting coroutine needs a
turn, and if not, that coroutine isn’t even woken up, avoiding any switching costs
completely.)
None of this information is new, and the problems with threading as a programming model are
not platformspecific either. For example, this is what the Windows MSDN programming
guidelines documentation says about threading:
The central concurrency mechanism in the Windows API is the thread. You typically use the
CreateThread function to create threads. Although threads are relatively easy to create and
use, the operating system allocates a significant amount of time and other resources to manage
them. Additionally, although each thread is guaranteed to receive the same execution time as
any other thread at the same priority level, the associated overhead requires that you create
sufficiently large tasks. For smaller or more finegrained tasks, the overhead that is associated
with concurrency can outweigh the benefit of running the tasks in parallel.6
—MSDN Programming Guidelines, Comparing the Concurrency Runtime to
Other Concurrency Models
But—I hear you protest—this is Windows, right? Surely a UNIX system doesn’t have these
problems? Here follows a similar recommendation from the Mac Developer Library:
Threading has a real cost to your program (and the system) in terms of memory use and
performance. Each thread requires the allocation of memory in both the kernel memory space
and your program’s memory space. The core structures needed to manage your thread and
coordinate its scheduling are stored in the kernel using wired memory. Your thread’s stack
space and perthread data is stored in your program’s memory space. Most of these structures
are created and initialized when you first create the thread—a process that can be relatively
expensive because of the required interactions with the kernel.7
—Mac Developer Library, Threading Programming Guide
Next we look at a case study involving threads in which I intend to highlight the first and most
important point.
At the start of this book we heard the story of a restaurant in which humanoid robots—
ThreadBots—did all the work. In that analogy, each worker was a thread. In this case study
we’re going to look at why threading is considered unsafe.
import threading
from queue import Queue
class ThreadBot(threading.Thread):
def __init__(self):
super().__init__(target=self.manage_table)
self.cutlery = Cutlery(knives=0, forks=0)
self.tasks = Queue()
def manage_table(self):
while True:
task = self.tasks.get()
if task == 'prepare table':
kitchen.give(to=self.cutlery, knives=4, forks=4)
elif task == 'clear table':
self.cutlery.give(to=kitchen, knives=4, forks=4)
elif task == 'shutdown':
return
The target function of the thread is the manage_table() method, defined further below.
This bot is going to be waiting tables, and will need to be responsible for some cutlery. Each
bot keeps track of the cutlery that it took from the kitchen here. (The Cutlery class will be
defined later.)
The bot will also be assigned tasks. They will be added to this task queue, and the bot will
perform them during its main processing loop, next.
The primary routine of this bot is this infinite loop. If you need to shut a bot down, you have
to give them the "shutdown" task.
There are only three tasks defined for this bot. This one, "prepare table" is what the
bot must do to get a new table ready for service. For our test, the only requirement is to get
sets of cutlery from the kitchen and place them on the table. "clear table" is for when
a table is to be cleared: the bot must return the used cutlery back to the kitchen.
"shutdown" just shuts the bot down.
@attrs
class Cutlery:
knives = attrib(default=0)
forks = attrib(default=0)
import sys
for bot in bots:
for i in range(int(sys.argv[1])):
bot.tasks.put('prepare table')
bot.tasks.put('clear table')
bot.tasks.put('shutdown')
attrs, which is an open source Python library that has nothing to do with threads or
asyncio, is a really wonderful library for making class creation easy. Here, the @attrs
decorator will ensure that this Cutlery class will get all the usual boilerplate code (like
__init__()) automatically set up.
The attrib function provides an easy way to create attributes, including defaults, which
you might normally have handled as keyword arguments in the __init__() method.
This method is used to transfer knives and forks from one Cutlery object to another.
Typically it will be used by bots to obtain cutlery from the kitchen for new tables, and to
return the cutlery back to the kitchen after the table is cleared.
This is a very simple utility function for altering the inventory data in the object instance.
We’ve defined kitchen as the identifier for the kitchen inventory of cutlery. Typically,
each of the bots will obtain cutlery from this location. It is also required that they return
cutlery to this store when a table is cleared.
This script is executed when testing. For our test we’ll be using 10 threadbots.
We get the number of tables as a commandline parameter, and then give each bot that
number of tasks for preparing and clearing tables in the restaurant.
The “shutdown” will make the bots stop (so that bot.join() a bit further down will
return). The rest of the script prints diagnostic messages and starts up the bots.
Your strategy for testing the code basically involves running a group of threadbots over a
sequence of table service. Each threadbot must:
prepare a “table for four,” which means obtaining four sets of knives and forks from the
kitchen;
clear a table, which means returning the set of four knives and forks from a table back to
the kitchen.
If you run a bunch of threadbots over a bunch of tables a specific number of times, you expect
that after all the work is done, all of the knives and forks should be back in the kitchen and
accounted for.
Wisely, you decide to test that, and with 100 tables to be prepared and cleared for each
threadbot, and all of them operating at the same time, because you want to ensure that they can
work together and nothing goes wrong. This is the output of that test:
All the knives and forks end up back in the kitchen! So you congratulate yourself on writing
good code and deploy the bots. Unfortunately, in practice, every now and then you find that you
do not end up with all cutlery accounted for when the restaurant closes. You noticed the
problem gets worse when you add more bots and/or the place gets busier. Frustrated, you run
your tests again, changing nothing except the size of the test (10,000 tables!):
Oops. Now you see that there is indeed a problem. With 10,000 tables served, you end up with
the wrong number of knives and forks left in the kitchen. For reproducibility, you check that the
error is consistent:
$ python cutlery_test.py 10000
Kitchen inventory before service: Cutlery(knives=100, forks=100)
Kitchen inventory after service: Cutlery(knives=112, forks=96)
There are still errors, but by different amounts compared to the previous run. That’s just
ridiculous! Remember, these bots are exceptionally wellconstructed and they don’t make
mistakes. What could be going wrong?
Discussion
Your ThreadBot code is very simple and easy to read. The logic is fine.
You even have a working test (with 100 tables) that reproducibly passes.
You have a longer test (with 10,000 tables) that reproducibly fails.
These are a few typical signs of a racecondition bug. Experienced readers will already have
seen the cause, so let’s investigate that now. It all comes down to this method inside our
Cutlery class:
The inline summation, +=, is implemented internally (inside the C code for the Python
interpreter itself) as a few separate steps:
2. Add the new value, knives to the value in that temporary location.
3. Copy the new total from the temporary location back into the original location.
The problem with preemptive multitasking is that any thread busy with the steps above can be
interrupted at any time, and a different thread can be given the opportunity to work through the
same steps.
In this case, ThreadBot A might do step 1, then the OS scheduler pauses A and switches to
ThreadBot B, and B also reads the current value of self.knives, then execution goes back
to A, and it increments and writes back its new total—but then B continues from where it got
paused (after step 1), and then increments and writes back its new total, thereby erasing the
change made by A!
WARNING
If describing this race condition sounds complex, please keep in mind that this
example (of a race condition) is just about the simplest possible case. We were able
to check all the code, and we even have tests that can reproduce the problem on
demand. In the real world, in large projects, try to imagine how much more difficult
it can become.
This problem can be fixed by placing a lock around the modification of the shared state
(imagine we added a threading.Lock to the Cutlery class):
But this requires you to know all the places where state will be shared between multiple threads.
This approach is viable when you control all the source code, but it becomes very difficult when
many thirdparty libraries are used—which is likely in Python thanks to the wonderful open
source ecosystem.
Note that it was not possible to see the race condition by looking at the source code alone. This
is because the source code provides no hints about where execution is going to switch between
threads. That wouldn’t be useful anyway, because the OS can switch between threads just about
anywhere.
Another, much better, solution—and the point of async programming—is that we modify our
code so that we use only one ThreadBot and configure it to move between all the tables as
necessary. For our case study, this means that the knives and forks in the kitchen will only ever
get modified by a single thread.
And even better: in our async programs we’ll be able to see exactly where context will switch
between multiple concurrent coroutines, because the await keyword indicates such places
explicitly. I’ve decided against showing an async version of this case study here, because the
next chapter is going to explain how to use asyncio in depth. But if your curiosity is
insatiable, there is an annotated example in the appendix; it’ll probably only make sense after
you read the next chapter!
1 https://curio.readthedocs.io/en/latest/devel.html#pleasedontusecurio
2 http://bit.ly/2CFOv8a
3 The theoretical address space for a 32bit process is 4 GB, but the operating system
typically reserves some of that. Often, only 3 GB is left to the process as addressable virtual
memory, but on some operating systems it can be as low as 2 GB. Please take the numbers
mentioned in this section as generalizations and not absolutes. There are far too many
platformspecific (and historically sensitive) details to get into here.
4 http://blog.tsunanet.net/2010/11/howlongdoesittaketomakecontext.html
5 https://en.wikipedia.org/wiki/Context_switch#Cost
6 http://bit.ly/2Fr3eXK
7 https://apple.co/2BMeM83
8 https://apple.co/2owWwHM
9 http://bit.ly/2Fq9M8P
Playlists
Topics
Asyncio provides another tool for concurrent programming in Python, that is more lightweight
than threads or multiprocessing. In a very simple sense it does this by having an event loop
Tutorials
execute a collection of tasks, with a key difference being that each task chooses when to yield
control back to the event loop.1
Offers & Deals
—Philip Jones, Medium
The Asyncio
Highlights API in Python is complex because it aims to solve different problems for different
groups of people. Unfortunately, there is very little guidance available to help you figure out
Settings
which parts of asyncio are important for the group you’re in.
Support
My goal is to help you figure that out. There are two main target audiences for the async
features in Python:
Sign Out
Enduser developers
These want to make applications using asyncio. I am going to assume that you’re in this
group.
Framework developers
These want to make frameworks and libraries that enduser developers can use in their
applications.
Much of the confusion around asyncio in the community today is due to confusion between
these two goals. For instance, the documentation for asyncio in the official Python
documentation is more appropriate for framework developers, not end users. This means that
enduser developers reading those docs quickly become shellshocked by the apparent
complexity. You’re somewhat forced to take it all in before being able to do anything with it.
It is my hope that this book can help to separate, in your mind, the features of asyncio that are
important for enduser developers and those important for framework developers.
TIP
My goal is to give you only the most basic understanding of the building blocks of Asyncio;
enough that you should be able to write simple programs with it, and certainly enough that you
will be able to dive into more complete references.2
First up, we have a “quickstart” section that aims to provide the most important building blocks
for asyncio applications.
Quickstart
You only need to know about seven functions to use asyncio [for everyday use].
—Yury Selivanov, author of PEP 492
It’s pretty scary to open the official documentation for Asyncio. There are many sections with
new, enigmatic words, and concepts that will be unfamiliar to even experienced Python
programmers, as asyncio is a very new thing. We’re going to break all that down and explain
how to approach the asyncio documentation later, but for now you need to know that the
actual surface area you have to worry about with the asyncio library is much smaller than it
seems.
Yury Selivanov, the author of PEP 492 and allround major contributor to async Python,
explained in his talk async/await in Python 3.5 And Why It Is Awesome, presented at PyCon
2016 that many of the APIs in the asyncio module are really intended for framework
designers, not enduser developers. In that talk, he emphasized the main features that end users
should care about, and these are a small subset of the whole asyncio API.
In this section we’re going to look at those core features, and see how to hit the ground looping
with eventbased programming in Python.
You’ll need a basic knowledge of coroutines (presented in the section after this one), but except
for that, if you want to be able to make use of the asyncio library as an enduser developer
(and not a framework designer), these are the things you need to know, with a tiny example:
Example 31. The “Hello World” of Asyncio
# quickstart.py
import time
import asyncio
loop = asyncio.get_event_loop()
loop.create_task(main())
loop.run_forever()
pending = asyncio.Task.all_tasks(loop=loop)
group = asyncio.gather(*pending, return_exceptions=True)
loop.run_until_complete(group)
loop.close()
Output:
$ python quickstart.py
Sun Sep 17 14:17:37 2017 Hello!
Sun Sep 17 14:17:38 2017 Goodbye!
loop = asyncio.get_event_loop()
You need a loop instance before you can run any coroutines, and this is how you get one. In
fact, anywhere you call it, get_event_loop() will give you the same loop instance
each time, as long as you’re using only a single thread.3
task = loop.create_task(coro)
In the code above, the specific invocation is loop.create_task(main()). Your
coroutine function will not be executed until you do this. We say that create_task()
schedules your coroutine to be run on the loop. The returned task object can be used to
monitor the status of the task, for example whether it is still running or has completed, and
can also be used to obtain a result value from your completed coroutine. You can also cancel
the task with task.cancel().4
CAUTION
asyncio in Python exposes a great deal of the underlying machinery around the event loop—
and requires you to be aware of things like the event loop and its lifetime management. This is
different from Node.js for example, which also contains an event loop, but keeps it somewhat
hidden away. However, once you’ve worked with asyncio for bit, you’ll begin to notice that
the pattern for starting up and shutting down the event loop doesn’t stray terribly far from the
code above. And in the remainder of this book we will examine some of the nuances around
loop lifetime in more detail too.
I left something out in the example above. The last item of basic functionality you’ll need to
know is how to run blocking functions. The thing about cooperative multitasking is that you
need all I/Obound functions to…well, cooperate, and that means allowing a context switch
back to the loop using the keyword await. Most of the Python code available in the wild today
does not do this, and instead relies on you to run such functions in threads. Until there is more
widespread support for async def functions, you’re going to find that using such blocking
libraries is unavoidable.
For this, asyncio provides an API that is very similar to the API in the
concurrent.futures package. This package provides a ThreadPoolExecutor and a
ProcessPoolExecutor. The default is threadbased, but is easily replaced with a process
based one. I omitted this from the previous example because it would have obscured the
description of how the fundamental parts fit together. Now that those are covered, we can look
at the executor directly.
There are a couple of quirks to be aware of. Let’s have a look at a code sample:
# quickstart_exe.py
import time
import asyncio
def blocking():
time.sleep(0.5)
print(f"{time.ctime()} Hello from a thread!")
loop = asyncio.get_event_loop()
loop.create_task(main())
loop.run_in_executor(None, blocking)
loop.run_forever()
pending = asyncio.Task.all_tasks(loop=loop)
group = asyncio.gather(*pending)
loop.run_until_complete(group)
loop.close()
Output:
$ python quickstart_exe.py
Sun Sep 17 14:17:38 2017 Hello!
Sun Sep 17 14:17:38 2017 Hello from a thread!
Sun Sep 17 14:17:39 2017 Goodbye!
Exploring the Variety of Random
Documents with Different Content
The Project Gutenberg eBook of Naisten
kasvatuksesta
This ebook is for the use of anyone anywhere in the United States
and most other parts of the world at no cost and with almost no
restrictions whatsoever. You may copy it, give it away or re-use it
under the terms of the Project Gutenberg License included with this
ebook or online at www.gutenberg.org. If you are not located in the
United States, you will have to check the laws of the country where
you are located before using this eBook.
Language: Finnish
Havaintoja ja mietteitä
Kirj.
LUCINA HAGMAN
Jos lapsi oli tyttö, ei kuultu laulaa eikä vietetty juhlallisuutta; heidän
uskontonsa ei puhunut mitään millä: hän oli elämään vastaan
otettava. Tytöllä ei ollut mitään merkitystä suvun taikka kansan
kehityksessä. Kunnioituksen sijaan sai äiti kokea kylmyyttä ja
ylenkatsetta mieheltään. Jos hän synnytti ainoastaan tyttöjä oli
miehellä oikeus hyljätä hänet yhdentoista vuoden jälkeen. —
Athenassa oli isän tapana poikalapsen synnyttyä koristaa porttinsa
öljylehden seppeleillä, sillä tavoin kaikille ohikulkijoille
osoittaaksensa sitä onnea, joka häntä oli kohdannut. Jos lapsi oli
tyttö käski hän palvelijansa ripustamaan kehrävarsi porttiinsa.
Spartalaiset pitivät tytön sukua miltei viallisuutena. Kymmenestä
lapsesta, jotka heitettiin kuolemaan oli tavallisesti seitsemän tyttöjä.
Roomassa oli tapana asettaa vastasyntynyt lapsi isän jalkain
juureen. Varjeluksen merkiksi nosti isä lapsen ylös; mutta jätti
paikoilleen ja meni tiehensä, jos aikoi sen hyljätä. Monista
kuninkaista ja ruhtinoista keskiajalta kertoo historia, miten he kun
kohtalo ei heille suonut poikia, hylkäsivät ja luotaan karkoittivat
tyttölapsensa.
Our website is not just a platform for buying books, but a bridge
connecting readers to the timeless values of culture and wisdom. With
an elegant, user-friendly interface and an intelligent search system,
we are committed to providing a quick and convenient shopping
experience. Additionally, our special promotions and home delivery
services ensure that you save time and fully enjoy the joy of reading.
textbookfull.com