CS1602Introduction to Computation
Lecture 16Part 6 Pythonic Python and Modern PracticeAI Level 2

Multitasking, Review and Beyond

The last lecture. First one remaining piece of the picture — how a program does several things at once — then sixteen weeks assembled into one map, and finally where this course leads.

By the end of this lecture you should be able to

  • State the difference between concurrency and parallelism, and what the GIL is
  • Decide whether a task wants threads, processes, or neither
  • Assemble the whole course into a single structure
  • Know what the final exam covers
  • Name the directions this course opens up
Contents

1. Multitasking

Your programs have been a single line

Every program you have written runs as one line of execution: start at the first statement, proceed downward, finish one thing before starting the next.

Real programs frequently need to do several things at once:

  • a browser downloads images, renders the page, and responds to your clicks
  • a server handles thousands of users simultaneously
  • scientific computing splits one large task across eight CPU cores

This section fills that gap — concepts only; no implementation is required of you.

Concurrency and parallelism

The words get used interchangeably and mean different things:

ConcurrencyParallelism
meaningalternating between several thingsdoing several things simultaneously
cores neededone is enoughmore than one, necessarily
analogyone cook moving between three hobsthree cooks, one hob each
what it solvesnot wasting time while waitingtoo much computation

One person switching rapidly between tasks, appearing simultaneous — that is concurrency. Several people genuinely working at once — that is parallelism.

Two kinds of task, two kinds of bottleneck

Deciding whether multitasking helps starts with where your task is stuck:

KindBottleneckExamples
I/O-boundwaiting on something externalreading files, fetching pages, querying a database
CPU-boundthe computation itselfmatrix work, image processing, cracking passwords

An I/O-bound task spends most of its time waiting. While it waits the CPU is idle — switching to something else during that gap is free money.

A CPU-bound task is always computing. Switching between tasks gains nothing, because one core is only so fast. You need more cores.

import time

# CPU-bound: always computing, every second spent on arithmetic
def cpu_task() -> int:
    return sum(i * i for i in range(200_000))

t = time.time()
for _ in range(5):
    cpu_task()
cpu_time = time.time() - t

print(f"5 CPU tasks in sequence: {cpu_time:.3f} s")
print("All five hold the CPU throughout - only more cores will speed this up")
print()
print("For contrast: five I/O tasks each waiting 0.1 s take 0.5 s in sequence,")
print("and for almost all of that half-second the CPU sits idle. That idle time")
print("is exactly what concurrency picks up.")

Python’s GIL

Now for something specific to Python.

The GIL (Global Interpreter Lock) is a lock inside CPython guaranteeing that only one thread executes Python bytecode at any moment.

The consequence is direct: in CPython, threads cannot speed up CPU-bound work. Start eight threads doing matrix arithmetic and they still queue for one core.

Three approaches

ApproachModuleSuitsAffected by the GIL
threadsthreadingI/O-boundyes (but released while waiting on I/O)
processesmultiprocessingCPU-boundno (each process has its own interpreter)
asyncasynciolarge numbers of concurrent I/Oyes (but it uses no threads at all)

Threads: for waiting

A thread waiting on I/O releases the GIL, letting others run. So threads genuinely help I/O-bound work.

import threading
import time

def download(name: str) -> None:
    print(f"  starting {name}")
    time.sleep(1)             # pretend to download
    print(f"  finished {name}")

threads = [threading.Thread(target=download, args=(f"file{i}",)) for i in range(3)]

start = time.time()
for t in threads:
    t.start()
for t in threads:
    t.join()                  # wait for all of them
print(f"three downloads took {time.time() - start:.1f} s (not 3)")

Three one-second downloads take about one second, because they wait simultaneously.

Processes: for computing

Each process has its own interpreter and its own GIL, so they genuinely run in parallel.

from multiprocessing import Pool

def heavy(n: int) -> int:
    return sum(i * i for i in range(n))

if __name__ == "__main__":       # multiprocessing requires this (see L10)
    with Pool(4) as pool:
        results = pool.map(heavy, [1_000_000] * 4)
    print(results)

The cost is that processes share no memory — data is serialized back and forth, far more expensive than with threads. If the tasks are not heavy enough, starting the processes costs more than it saves.

Async: one thread, enormous numbers of I/O operations

import asyncio

async def fetch(name: str) -> str:
    await asyncio.sleep(1)        # yield control while waiting
    return f"{name} done"

async def main() -> None:
    results = await asyncio.gather(fetch("A"), fetch("B"), fetch("C"))
    print(results)

asyncio.run(main())

async / await let one thread voluntarily give up control while waiting and do something else. No threads means no GIL contention and no thread-switching cost — which is why nearly all modern network servers work this way.

Choosing

Where is your task stuck?
├── waiting on I/O (network, files, database)
│   ├── a handful of tasks       → threading
│   └── hundreds or thousands    → asyncio
├── computing slowly (CPU-bound)
│   └── multiprocessing
└── neither → do not multitask
# illustrating a race condition: two "threads" incrementing one variable
counter = 0

def increment_unsafe() -> None:
    global counter
    temp = counter        # read
    temp = temp + 1       # compute
    counter = temp        # write

print("Those three steps are not atomic. In genuine multithreading,")
print("two threads may read the same value, each add one, and one increment is lost.")
print("The fix is a lock, and locks in turn can deadlock.")

2. A map of sixteen weeks

Six parts, one line

Part I   Foundations of Computation      L1-L4
         what computation is → what data is → how to express it → how to control flow
         closes on: writing a complete program with input, branching, loops, functions

Part II  Built-in Data Structures        L5-L6
         one variable is not enough → five containers → which to pick
         closes on: choosing the right container

Part III Problem Solving with AI         L7-L8
         making problems smaller (recursion) → judging code (AI collaboration)
         closes on: decomposing problems and reviewing code

Part IV  Abstraction and Organization    L9-L10
         packaging data with behavior → relationships and files (inheritance, modules)
         closes on: structuring a medium-sized program

Part V   Robustness and the Real World   L11-L12
         too slow / crashes / quietly wrong → external data is dirty
         closes on: handling real data

Part VI  Pythonic Python                 L13-L16
         iteration and generators → advanced functions → AI at project scale → closing
         closes on: writing idiomatic Python and delivering a real project

Threads running through everything

Some ideas belong to no single lecture but recur, deepening each time:

One: the reference model. L5 variables as arrows → L6 mutable arguments get modified → L9 mutable class variables → L10 == versus is → L14 mutable defaults. One idea with five faces.

Two: cost awareness. L5 intuition about list operations → L6 choosing containers → L11 big-O made precise → L13 space and generators → L15 an AI will not judge complexity for you.

Three: divide and conquer. L3 functions → L7 recursion → L9 classes → L10 modules → L15 decomposing requirements. One idea at five scales.

Four: verification. L3 print versus return → L8 tests rather than plausibility → L11 pytest and assertions → L15 AI tests cannot verify AI code.

Five: the outside world is dirty. L3 input is always a string → L11 exception handling → L12 encodings, bad rows, paths.

The frequent traps

These were emphasized repeatedly and are the likeliest source of mistakes in both exams and practice:

TrapWhere
never compare floats with ==L2
/ always returns a floatL2
input() always returns a stringL3
inside a function, return rather than printL3
parenthesize bitwise operations mixed with arithmeticL4
mutable default argumentsL4, L14
in-place methods return NoneL5
[[0]*n]*m shares one rowL5
never modify a container while iterating itL5, L6
(1) is not a tuple; (1,) isL6
dictionary keys must be immutableL6
a recursion missing its base caseL7
forgetting selfL9
forgetting super().__init__()L10
is only against NoneL10
+= on strings in a loop is O(n²)L11
a bare except:L11
assertions cannot validate user inputL11
"w" truncates the fileL12
omitting encodingL12
a generator can only be traversed onceL13
late binding in lambdasL14
an assignment makes a name local throughoutL14

3. The final exam

Format

Closed book, 50% of the final grade.

Scope

Everything from L1 to L16, unevenly weighted:

ContentApproximate weight
basic syntax and data types (L2–L4)20%
containers and choosing among them (L5–L6)20%
recursion (L7)10%
classes and modules (L9–L10)15%
complexity, exceptions, testing (L11)15%
files and text processing (L12)10%
iterators, generators, advanced functions (L13–L14)10%

Not examined: L15’s AI collaboration method (assessed through the project), L16’s multitasking (concepts only, no implementation), and elaborate regular expressions (basic notation only).

Question types

  • Read code, give the output — the frequent-traps table is the source for these
  • Find and fix — given buggy code, identify and correct the problem
  • Write a function — given a signature and examples. By hand, edge cases included
  • State the complexity — of a given fragment, with justification
  • Short concept answers — for instance “the difference between == and is”, “what is duck typing”

4. Where this leads

What you can do now

Sixteen weeks ago many of you had never written a line of code. Now you can:

  • turn a problem stated in plain language into a working program
  • pick the right data structure and estimate what it costs
  • read someone else’s code, judge its quality, and find its bugs
  • handle real, unclean data
  • write tests that verify your own judgment
  • work with an AI rather than being led by one

That is not “knowing Python”. That is knowing how to solve problems. The language is only the vehicle.

Directions from here

Data science and machine learning

Your team project is NumPy in embryo. The route from here: NumPy → pandas (tabular data) → Matplotlib (visualization) → scikit-learn (machine learning) → PyTorch (deep learning)

If AI itself interests you, this is the most direct path.

Algorithms and data structures

L11 was only an opening. A real algorithms course covers the full range of sorting and searching, trees and graphs, dynamic programming, greedy methods.

This is the core of computer science, and the most heavily examined material in interviews. Start with the easy problems on LeetCode.

Software engineering

One person writing hundreds of lines and a team maintaining hundreds of thousands are different problems. You would learn: version control (Git), code review, continuous integration, design patterns, architecture.

Every friction you met in the team project is what this discipline exists to address.

Systems and the layer below

L1’s von Neumann architecture, L10’s bytecode, this lecture’s GIL — all are doorways downward. Operating systems, compilers, computer architecture.

Learning some C or Rust will change how you see that layer entirely.

Other languages

Your second language comes far faster than your first, because languages share far more than they differ:

LanguageWorth learning when
Cyou want to understand memory and pointers, and what sits under Python
JavaScriptyou want to build for the web
Rustyou want C’s performance with safety guarantees
SQLyou will touch a database — which almost every direction does

Finally

L1 quoted the Zen of Python. Read it once more and see whether it lands differently:

import this

Back then I said these would read as platitudes, and that they would read differently after L13.

Now you know what Simple is better than complex refers to — you have seen nine lines collapse into three. You know Readability counts is not a pleasantry — you have read your partner’s code. And you understand why There should be one obvious way to do it keeps coming up — you chose between map/filter and a comprehension.

Good luck in the exam, and good luck with everything you write afterward.


Summary

Multitasking

  • Concurrency alternates; parallelism is simultaneous
  • I/O-bound wants threads or async; CPU-bound wants processes
  • The GIL means CPython threads cannot rescue CPU-bound work
  • Multitasking brings races, deadlocks and irreproducible bugs — first ask whether the single-threaded version can be made faster

The course

  • Six parts, five threads: the reference model, cost awareness, divide and conquer, verification, and the dirtiness of the outside world
  • Revise along the threads, not lecture by lecture
  • Be able to explain every entry in the frequent-traps table

Exercises

  1. For each task, say whether it wants threading, multiprocessing, asyncio or none, and why:
    • fetching 500 web pages at once
    • resizing 10,000 images
    • reading a 100 MB CSV and computing statistics
    • a chat server handling 1,000 simultaneous users
  2. Explain why “eight threads doing matrix multiplication” gains almost nothing in CPython.
  3. Pick one of the five threads and write a page tracing it end to end, naming the lecture and giving a code example at each step.
  4. Take any ten entries from the frequent-traps table and write the smallest program that triggers each.
  5. Take a lab problem you solved earlier and rewrite it with what you know now — shorter, faster, or clearer.
  6. Write down which direction you intend to take and what you plan to learn next. There is no right answer, but it is worth writing once, seriously.