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
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Tweak run-examples.py to run the newer example code.
  • Loading branch information
ericsnowcurrently committed Sep 23, 2024
commit 10ee3699d7d520e91d4f76a8b0738ef5ba645e55
358 changes: 319 additions & 39 deletions Doc/includes/concurrency/run-examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@
:start-after: and :end-before: options.
"""

from collections import namedtuple
import os.path
import re
import shlex
import subprocess
import sys


IMPLS_DIR = os.path.join(os.path.dirname(__file__), 'concurrency')
if IMPLS_DIR not in sys.path:
sys.path.insert(0, IMPLS_DIR)
IMPLS_DIR = os.path.dirname(__file__)
#if IMPLS_DIR not in sys.path:
# sys.path.insert(0, IMPLS_DIR)


class example(staticmethod):
Expand Down Expand Up @@ -348,47 +352,323 @@ def run_using_cf_multiprocessing():
# [end-w3-cf]


ALL = object()


def registry_resolve(registry, name, kind=None):
orig = name
if name is None:
name = ''
while True:
try:
return name, registry[name]
except KeyError:
# Try aliases.
try:
name = registry[f'|{name}|']
except KeyError:
raise ValueError(f'unsupported {kind or "???"} registry key {name!r}')


def registry_resolve_all(registry, name=ALL, kind=None):
if name is ALL or name is registry:
for pair in registry.items():
name, _ = pair
if name.startswith('|'):
continue
yield pair
else:
yield registry_resolve(registry, name, kind)


def registry_render(registry, kind=None, indent='', vfmt=None, parent=None):
kind = f'{kind} - ' if kind else ''
entries = {}
aliases_by_target = {}
default = None
for name, value in registry.items():
if name == '||':
default, _ = registry_resolve(registry, value)
elif name.startswith('|'):
target, _ = registry_resolve(registry, value)
try:
aliases = aliases_by_target[target]
except KeyError:
aliases = aliases_by_target[target] = []
aliases.append(name.strip('|'))
else:
entries[name] = value
for name, spec in entries.items():
label = f'{parent}.{name}' if parent else name
line = f'{indent}* {kind}{label}'
if vfmt:
specstr = vfmt(spec, name) if callable(vfmt) else vfmt.format(spec)
line = f'{line:30} => {specstr}'
if name == default:
line = f'{line:60} (default)'
aliases = aliases_by_target.get(name)
if aliases:
line = f'{line:70} (aliases: {", ".join(aliases)})'
yield line, name, spec


EXAMPLES = {
'grep': {
'IMPLS': {
'||': 'sequential',
'sequential': {
'||': 'basic',
'basic': '<>',
},
'threads': {
'||': 'basic',
'basic': '<>',
'cf': '<>',
},
'interpreters': {
'||': 'basic',
'basic': '<>',
#'cf': '<>',
},
'|async|': 'asyncio',
'asyncio': {
'||': 'basic',
'basic': '<>',
},
'multiprocessing': {
'||': 'basic',
'basic': '<>',
#'cf': '<>',
},
},
'OPTS': {
'||': 'min',
'|min|': 'min.all',
'min.all': "-r '.. index::' .",
'min.mixed': 'help Makefile make.bat requirements.txt',
},
},
}


class Selection(namedtuple('Selection', 'app model impl')):

_opts = None
_cmd = None
_executable = None
_subargv = None
_subargvstr = None
_defaultimpl = None

REGEX = re.compile(rf"""
^
( \* | \w+ ) # <app>
(?:
\.
( \* | \w+ ) # <model>
(?:
\.
( \* | \w+ ) # <impl>
)?
)?
(?:
:
( \S+ (?: \s+ \S+ )? ) # <opts>
)?
\s*
$
""", re.VERBOSE)

@classmethod
def parse(cls, text):
m = cls.REGEX.match(text)
if not m:
raise ValueError(f'unsupported selection {text!r}')
app, model, impl, opts = m.groups()

if app == '*':
app = ALL
if model == '*':
model = ALL
if impl == '*':
impl = ALL
if opts == '*':
opts = ALL
self = cls(app, model, impl, opts)
return self

@classmethod
def _from_resolved(cls, app, model, impl, opts, cmd, argv,
defaultimpl=False,
):
self = cls.__new__(cls, app, model, impl, opts)
if cmd == '<>':
cmd = f'{app}-{model}' if defaultimpl else f'{app}-{model}-{impl}'
executable = os.path.join(IMPLS_DIR, cmd + '.py')
else:
executable = cmd
self._cmd = cmd
self._executable = executable
if isinstance(argv, str):
self._subargvstr = argv
else:
self._subargv = tuple(argv)
self._defaultimpl = defaultimpl
return self

def __new__(cls, app, model, impl, opts=None):
self = super().__new__(cls, app, model or None, impl or None)
self._opts = opts or None
return self

@property
def opts(self):
return self._opts

@property
def cmd(self):
for bad in (ALL, None):
if bad in self or self._opts is bad:
raise RuntimeError('not resolved')
return self._cmd

@property
def executable(self):
for bad in (ALL, None):
if bad in self or self._opts is bad:
raise RuntimeError('not resolved')
return self._executable

@property
def subargv(self):
for bad in (ALL, None):
if bad in self or self._opts is bad:
raise RuntimeError('not resolved')
if self._subargv is None:
self._subargv = tuple(shlex.split(self._subargvstr))
return self._subargv

@property
def argv(self):
for bad in (ALL, None):
if bad in self or self._opts is bad:
raise RuntimeError('not resolved')
argv = [self._executable]
if self._executable.endswith('.py'):
argv.insert(0, sys.executable)
if self._subargv is None:
self._subargv = shlex.split(self._subargvstr)
argv.extend(self._subargv)
return argv

def resolve(self, argv=None):
cls = type(self)
if self._cmd and self._cmd is not ALL:
assert self._argv is not ALL
if argv is None:
resolved = self
else:
default = self._defaultimpl
resolved = cls._from_resolved(
*self, self._opts, self._cmd, argv, default)
yield resolved
else:
assert self.app is not None
requested = argv
resolve = registry_resolve_all
for app, app_spec in resolve(EXAMPLES, self.app, 'app'):
models = app_spec['IMPLS']
all_opts = app_spec['OPTS']
for model, impls in resolve(models, self.model, 'model'):
default, _ = registry_resolve(impls, '')
for impl, cmd in resolve(impls, self.impl, 'impl'):
if requested:
argvs = [('???', requested)]
else:
argvs = resolve(all_opts, self._opts, 'opts')
isdefault = (impl == default)
for opts, argv in argvs:
yield cls._from_resolved(
app, model, impl, opts, cmd, argv, isdefault)


#######################################
# A script to run the examples
#######################################

USAGE = 'run-examples [-h|--help] [SELECTION ..] [-- ..]'
HELP = """
SELECTION is one of the following:

APP
APP.MODEL
APP.MODEL.IMPL
APP:OPTS
APP.MODEL:OPTS
APP.MODEL.IMPL:OPTS

where each field is a known name or the wildcard (*).
"""[:-1]

if __name__ == '__main__':
# Run (all) the examples.
argv = sys.argv[1:]
if argv:
classname, _, funcname = argv[0].rpartition('.')
requested = (classname, funcname)
else:
requested = None

div1 = '#' * 40
div2 = '#' + '-' * 39
last = None
for func, cls in example.registry:
if requested:
classname, funcname = requested
if classname:
if cls.__name__ != classname:
continue
if func.__name__ != funcname:
continue
else:
if func.__name__ != funcname:
if cls.__name__ != funcname:
continue
remainder = None
if '--' in argv:
index = argv.index('--')
remainder = argv[index+1:]
argv = argv[:index]
if '-h' in argv or '--help' in argv:
print(USAGE)
print(HELP)
print()
if cls is not last:
last = cls
print(div1)
print(f'# {cls.__name__}')
print(div1)
print()
print(div2)
print(f'# {func.__name__}')
print(div2)
print('Supported selection field values:')
print()
try:
func()
except Exception:
import traceback
traceback.print_exc()
indent = ' ' * 2
def render(reg, kind, depth=0, vfmt=None, parent=None):
for l, _, s in registry_render(
reg, kind, indent*depth, vfmt, parent):
yield l, s
for line, app, app_spec in registry_render(EXAMPLES, 'app'):
print(line)
app_impls = app_spec['IMPLS']
print(f'{indent}* concurrency models:')
for line, _ in render(app_impls, None, 2):
print(line.format(''))
print(f'{indent}* implementations:')
for model, impls in registry_resolve_all(app_impls, kind='model'):
default, _ = registry_resolve(impls, '')
def vfmt_impl(cmd, impl, app=app, model=model):
opts, argv = '???', ()
sel = Selection._from_resolved(
app, model, impl, opts, cmd, argv, impl==default)
if sel.cmd != sel.executable:
return os.path.basename(sel.executable)
else:
return sel.executable
for line, cmd in render(impls, None, 2, vfmt_impl, model):
print(line)
print(f'{indent}* argv options:')
for line, _ in render(app_spec['OPTS'], None, 2, f'{app} {{}}'):
print(line)
sys.exit(0)

if not argv:
argv = ['*.*.*:*']

div = '#' * 40
for text in argv:
sel = Selection.parse(text)
for sel in sel.resolve(remainder or None):
print()
print(div)
print('# app: ', sel.app)
print('# model: ', sel.model)
print('# impl: ', sel.impl)
print('# opts: ', sel.opts)
print('# script: ', sel.executable)
print('# argv: ', sel.app, shlex.join(sel.subargv))
print(div)
print()
proc = subprocess.run(sel.argv)
print()
print('# returncode:', proc.returncode)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy