Skip to content

gh-123152: Add a Concurrency Howto Page #123163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 81 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
4187872
Let "python3" be set at the commandline.
ericsnowcurrently Aug 19, 2024
424dc37
Add a NEWS entry.
ericsnowcurrently Aug 20, 2024
9ab68fa
Add the concurrency howto doc.
ericsnowcurrently Aug 19, 2024
fce455e
Add more explanation.
ericsnowcurrently Aug 20, 2024
28d8ac0
More explanation.
ericsnowcurrently Aug 20, 2024
503d829
Re-structure the comparison table.
ericsnowcurrently Aug 20, 2024
e5d904f
Add basic examples for each concurrency model.
ericsnowcurrently Aug 20, 2024
b7bb9a5
Clarify about "spook action at a distance".
ericsnowcurrently Aug 21, 2024
7efb9b4
Clarify about the "pain" threads can cause.
ericsnowcurrently Aug 21, 2024
daaa02b
Add references to the howto in the library docs.
ericsnowcurrently Aug 21, 2024
ad50bdd
Clean up the Python concurrency models section.
ericsnowcurrently Aug 21, 2024
3a82978
Add a section about concurrent.futures.
ericsnowcurrently Aug 21, 2024
560f26e
Make the examples runnable.
ericsnowcurrently Aug 21, 2024
4f49ed7
Revert changes to Makefile.
ericsnowcurrently Aug 22, 2024
5151e3b
Fix a typo.
ericsnowcurrently Aug 22, 2024
14e59f8
Fix sphinx warnings.
ericsnowcurrently Aug 22, 2024
0b27fd9
Adjust the references.
ericsnowcurrently Aug 22, 2024
0586c72
Fill out and restructure the comparisons.
ericsnowcurrently Aug 22, 2024
8df61ad
Fill out the section about Python theads.
ericsnowcurrently Aug 22, 2024
269eaac
Fill out the section about multiple interpreters.
ericsnowcurrently Aug 22, 2024
7754912
Fill out the section about multprocessing.
ericsnowcurrently Aug 22, 2024
2c9a793
Fill out the section about async/await.
ericsnowcurrently Aug 23, 2024
2975bbb
Shuffle the concurrency model order.
ericsnowcurrently Aug 23, 2024
b82a18b
Fill out the section about distributed concurrency.
ericsnowcurrently Aug 23, 2024
5e7f7c3
Drop an incomplete note.
ericsnowcurrently Aug 23, 2024
c06b843
Fix typos.
ericsnowcurrently Aug 23, 2024
e05e76e
Add a missing divider.
ericsnowcurrently Aug 23, 2024
e7144ec
Add a link to the "overhead" table.
ericsnowcurrently Aug 26, 2024
e131b83
Expand the general explanation of workloads.
ericsnowcurrently Aug 26, 2024
c601e27
Updates for the first two workload examples.
ericsnowcurrently Aug 26, 2024
5c05581
Do not worry about demonstrating a web service.
ericsnowcurrently Aug 27, 2024
a0a8ebb
Move the grep example up.
ericsnowcurrently Aug 27, 2024
c3d1688
Implement threads for the grep example.
ericsnowcurrently Aug 27, 2024
48ac79d
Implement grep using concurrent.futures.
ericsnowcurrently Aug 28, 2024
9a1a81c
Fix nested lists and add quadrants.
ericsnowcurrently Aug 29, 2024
03156b5
Implement grep using multiprocessing.
ericsnowcurrently Aug 29, 2024
e0d833e
Start reorganizing, and dump a bunch of explanation.
ericsnowcurrently Aug 29, 2024
6dd91e3
Clarify about Python-supported concurrency models.
ericsnowcurrently Sep 3, 2024
1e935df
Small clarifications and cleanup.
ericsnowcurrently Sep 3, 2024
4fdf9f5
Move the high-level APIs section down.
ericsnowcurrently Sep 3, 2024
cb843f6
Clarify about the comparisons.
ericsnowcurrently Sep 3, 2024
8a87617
Updates about free-threading caveats.
ericsnowcurrently Sep 3, 2024
288c4f4
Formatting and links for intro section.
ericsnowcurrently Sep 4, 2024
0b768ae
wording tweaks
ericsnowcurrently Sep 4, 2024
ab24e83
Update the section about multiple interpreters.
ericsnowcurrently Sep 4, 2024
f7f1769
Update the section about coroutines.
ericsnowcurrently Sep 4, 2024
fdf46cb
Update the sections about multi-processing.
ericsnowcurrently Sep 4, 2024
4cc62a1
Clear out the section on downsides.
ericsnowcurrently Sep 4, 2024
54bacb6
Update the section about shared resources.
ericsnowcurrently Sep 4, 2024
49ec62f
Normalize TODO comments.
ericsnowcurrently Sep 4, 2024
8fd8406
Move the explanations to the right places.
ericsnowcurrently Sep 4, 2024
fdcc128
Add sub-sections.
ericsnowcurrently Sep 4, 2024
3033875
Move the caveats subsections around.
ericsnowcurrently Sep 4, 2024
3e4ab6a
Fix formatting.
ericsnowcurrently Sep 4, 2024
f07c696
Drop a TODO.
ericsnowcurrently Sep 4, 2024
7a04361
Hide the incomplete portions of text.
ericsnowcurrently Sep 4, 2024
f9815bb
Tweak a table header.
ericsnowcurrently Sep 4, 2024
48551a7
Fix a table header.
ericsnowcurrently Sep 4, 2024
eac23f9
Fix the sidebar table of contents.
ericsnowcurrently Sep 16, 2024
3e1c53e
Expand the grep section and code.
ericsnowcurrently Sep 16, 2024
9f6ea1f
Make the examples more uniform.
ericsnowcurrently Sep 16, 2024
3aaa4d1
Fix some wording.
ericsnowcurrently Sep 17, 2024
3a7b0be
Reorganize the example code a little.
ericsnowcurrently Sep 17, 2024
36edcce
Split up the examples file.
ericsnowcurrently Sep 18, 2024
7b4af12
Tweak CLI.
ericsnowcurrently Sep 18, 2024
bc6e725
Simplify the examples.
ericsnowcurrently Sep 19, 2024
f4749f6
Drop all the context manager stuff.
ericsnowcurrently Sep 19, 2024
eaaecca
Implement the async example.
ericsnowcurrently Sep 20, 2024
ebf3130
Drop the text about do_search().
ericsnowcurrently Sep 20, 2024
21ff9e1
Fix a typo.
ericsnowcurrently Sep 23, 2024
3682348
Move the grep examples to individual files.
ericsnowcurrently Sep 23, 2024
10ee369
Tweak run-examples.py to run the newer example code.
ericsnowcurrently Sep 23, 2024
e6f0088
Drop the combined grep package.
ericsnowcurrently Sep 24, 2024
3f2637b
Fix the highlighting.
ericsnowcurrently Sep 24, 2024
9ce6ccf
cleanups
ericsnowcurrently Sep 24, 2024
10a6571
Fix typos and wording.
ericsnowcurrently Sep 24, 2024
793bfa0
Limit side-by-side example lines to 60 characters.
ericsnowcurrently Sep 24, 2024
3e19d7e
Fix the examples.
ericsnowcurrently Sep 24, 2024
f826c9c
Fix a ref.
ericsnowcurrently Sep 24, 2024
049f1de
lint
ericsnowcurrently Sep 25, 2024
7686e10
Merge branch 'main' into concurrency-howto
AA-Turner Apr 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,846 changes: 1,846 additions & 0 deletions Doc/howto/concurrency.rst

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Doc/howto/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Python Library Reference.
:maxdepth: 1
:hidden:

concurrency.rst
cporting.rst
curses.rst
descriptor.rst
Expand Down Expand Up @@ -53,6 +54,7 @@ General:

Advanced development:

* :ref:`concurrency-howto`
* :ref:`curses-howto`
* :ref:`freethreading-python-howto`
* :ref:`freethreading-extensions-howto`
Expand Down
202 changes: 202 additions & 0 deletions Doc/includes/concurrency/grep-asyncio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import os
import os.path
import re
import sys

import asyncio


async def search(filenames, regex, opts):
matches_by_file = asyncio.Queue()

async def do_background():
MAX_FILES = 10
MAX_MATCHES = 100

# Make sure we don't have too many coros at once,
# i.e. too many files open at once.
counter = asyncio.Semaphore(MAX_FILES)

async def search_file(filename, matches):
# aiter_lines() opens the file too.
lines = iter_lines(filename)
async for m in search_lines(
lines, regex, opts, filename):
await matches.put(match)
await matches.put(None)
# Let a new coroutine start.
counter.release()

async with asyncio.TaskGroup() as tg:
for filename in filenames:
# Prepare for the file.
matches = asyncio.Queue(MAX_MATCHES)
await matches_by_file.put(matches)

# Start a coroutine to process the file.
tg.create_task(
search_file(filename, matches),
)
await counter.acquire()
await matches_by_file.put(None)

background = asyncio.create_task(do_background())

# Yield the results as they are received, in order.
matches = await matches_by_file.get() # blocking
while matches is not None:
match = await matches.get() # blocking
while match is not None:
yield match
match = await matches.get() # blocking
matches = await matches_by_file.get() # blocking

await asyncio.wait([background])


async def iter_lines(filename):
if filename == '-':
infile = sys.stdin
line = await read_line_async(infile)
while line:
yield line
line = await read_line_async(infile)
else:
# XXX Open using async?
with open(filename) as infile:
line = await read_line_async(infile)
while line:
yield line
line = await read_line_async(infile)


async def read_line_async(infile):
# XXX Do this async!
# maybe make use of asyncio.to_thread()
# or loop.run_in_executor()?
return infile.readline()


async def search_lines(lines, regex, opts, filename):
try:
if opts.filesonly:
if opts.invert:
async for line in lines:
m = regex.search(line)
if m:
break
else:
yield (filename, None)
else:
async for line in lines:
m = regex.search(line)
if m:
yield (filename, None)
break
else:
assert not opts.invert, opts
async for line in lines:
m = regex.search(line)
if not m:
continue
if line.endswith(os.linesep):
line = line[:-len(os.linesep)]
yield (filename, line)
except UnicodeDecodeError:
# It must be a binary file.
return


def resolve_filenames(filenames, recursive=False):
for filename in filenames:
assert isinstance(filename, str), repr(filename)
if filename == '-':
yield '-'
elif not os.path.isdir(filename):
yield filename
elif recursive:
for d, _, files in os.walk(filename):
for base in files:
yield os.path.join(d, base)


if __name__ == '__main__':
# Parse the args.
import argparse
ap = argparse.ArgumentParser(prog='grep')

ap.add_argument('-r', '--recursive',
action='store_true')
ap.add_argument('-L', '--files-without-match',
dest='filesonly',
action='store_const', const='invert')
ap.add_argument('-l', '--files-with-matches',
dest='filesonly',
action='store_const', const='match')
ap.add_argument('-q', '--quiet', action='store_true')
ap.set_defaults(invert=False)

reopts = ap.add_mutually_exclusive_group(required=True)
reopts.add_argument('-e', '--regexp', dest='regex',
metavar='REGEX')
reopts.add_argument('regex', nargs='?',
metavar='REGEX')

ap.add_argument('files', nargs='+', metavar='FILE')

opts = ap.parse_args()
ns = vars(opts)

regex = ns.pop('regex')
filenames = ns.pop('files')
recursive = ns.pop('recursive')
if opts.filesonly:
if opts.filesonly == 'invert':
opts.invert = True
else:
assert opts.filesonly == 'match', opts
opts.invert = False
opts.filesonly = bool(opts.filesonly)

async def main(regex=regex, filenames=filenames):
# step 1
regex = re.compile(regex)
# step 2
filenames = resolve_filenames(filenames, recursive)
# step 3
matches = search(filenames, regex, opts)
matches = type(matches).__aiter__(matches)

# step 4

# Handle the first match.
async for filename, line in matches:
if opts.quiet:
return 0
elif opts.filesonly:
print(filename)
else:
async for second in matches:
print(f'{filename}: {line}')
filename, line = second
print(f'{filename}: {line}')
break
else:
print(line)
break
else:
return 1

# Handle the remaining matches.
if opts.filesonly:
async for filename, _ in matches:
print(filename)
else:
async for filename, line in matches:
print(f'{filename}: {line}')

return 0
rc = asyncio.run(main())

# step 5
sys.exit(rc)
Loading
Loading