一、单选题(10 题)
在 Python 中,线程相对进程,更准确的描述是( )。
A 线程是资源分配的基本单位
B 线程是 CPU 调度的基本单位,依附于进程存在
C 线程可以脱离进程单独运行
D 进程与线程都不共享内存
✓ 正确
解析:线程是 CPU 调度的基本单位,必须依附于进程存在。进程才是资源分配的基本单位。
创建并启动子线程的正确三步不包含( )。
A import threading
B threading.Thread(target=func, ...)
C 线程对象.start()
D threading.spawn(func)
✓ 正确
解析:threading.spawn() 不是 Python threading 模块的 API。正确三步:import → Thread() → start()。
关于 Thread 的 target 参数,正确的是( )。
A 应写成 target=func()
B 应写成 target=func
C target 必须是 lambda
D target 只能是字符串函数名
✓ 正确
解析:target=func 传函数引用。
target=func() 会在主线程先执行函数,把返回值传给 target。
对只有一个参数的任务函数,使用 args 传参时应写成( )。
A args=(5)
B args=(5,)
C args=5
D args=[5]
✓ 正确
解析:单元素元组必须加逗号
(5,)。
(5) 只是括号表达式,不是元组。
同一进程内多个线程对全局列表执行 append,一般( )。
A 不需要也不应该共享
B 可以共享,能看到同一份列表
C 每个线程有独立副本
D 必须先 fork 才能共享
✓ 正确
解析:线程共用同一地址空间,全局列表只有一份,所有线程都能看到。
多个线程并发对同一整型全局变量做 count = count + 1 可能出现结果小于理论值,主要是因为( )。
A Python 版本太低
B +1 非原子操作,存在调度交错导致丢更新
C global 写多了
D 线程不能访问全局变量
✓ 正确
解析:count += 1 实际是 读取→计算→写入 三步非原子操作,线程可能在中间被切换导致更新丢失。
互斥锁的典型用法正确的是( )。
A 每个线程各自 threading.Lock() 一把新锁
B 多个线程共用同一把 Lock 对象,在临界区 acquire/release 或用 with lock:
C 上锁后不必释放
D Lock 只能用于进程
✓ 正确
解析:多个线程必须共用同一把 Lock 对象,配合
with lock: 或 acquire/release 保护共享数据。
CPython 中的 GIL 主要指( )。
A 用户创建的 threading.Lock
B 解释器层的全局锁,同一时刻通常只有一个线程执行 Python 字节码
C 网络全局 IP
D 生成器的全局变量
✓ 正确
解析:GIL(全局解释器锁)是 CPython 解释器层的锁,限制同一时刻只有一条线程执行 Python 字节码。
下列能得到一个生成器对象的是( )。
A g = [i for i in range(3)]
B g = (i for i in range(3))
C g = {i for i in range(3)}
D g = list(range(3))
✓ 正确
解析:列表推导式用[]、集合推导式用{},生成器推导式用()括号。
关于 async / await 与 asyncio.run,正确的是( )。
A async def 定义的函数一调用就会执行完
B await 用于等待可等待对象并把控制权交回事件循环
C asyncio.run 可以在任意嵌套函数里随意连续调用
D 协程运行在独立进程里
✓ 正确
解析:await 挂起当前协程等待可等待对象,同时把控制权交回事件循环。
二、填空题(10 题)
创建线程类:threading.Thread(target=, args=..., kwargs=...)。
✓ 正确
解析:target 参数传函数引用(可调用对象),不加括号。
主线程默认会等待所有线程结束后再结束进程(常规情况)。
✓ 正确
解析:主线程默认等待所有非守护子线程结束。
若希望主线程结束时子线程不再继续执行,可设置线程(daemon=True)。
✓ 正确
解析:daemon=True 将线程设为守护线程,主线程结束时守护线程自动终止。
互斥锁对象的创建方式常为 lock = threading.()。
✓ 正确
解析:threading.Lock() 创建互斥锁对象。
含 yield 的函数称为生成器函数,调用该函数得到的是。
✓ 正确
解析:调用生成器函数返回生成器对象,不是立即执行函数体。
生成器耗尽后再 next,会抛出异常;for 遍历时会自动处理。
✓ 正确
解析:生成器元素耗尽后调用 next() 抛出 StopIteration 异常,for 循环自动捕获。
协程三要素:async def、、顶层常用 asyncio.run(...)。
✓ 正确
解析:协程三要素:async def 定义、await 挂起等待、asyncio.run 启动事件循环。
在同一协程函数里要并发执行多个协程,常用 asyncio. 包装成任务。
✓ 正确
解析:asyncio.create_task() 将协程包装为任务实现并发调度。
三、简答题(7 题)
进程与线程是什么关系?为什么说"没有进程就没有线程"?
查看参考答案 ▼
线程必须依附在进程里运行:进程向操作系统申请地址空间、文件描述符等资源,线程是在该进程内部被调度执行的执行流。没有进程作为"容器"和资源边界,线程无法单独存在,因此说没有进程就没有线程。
✓ 正确
参考答案:线程必须
依附在进程里运行:进程向操作系统申请地址空间、文件描述符等资源,线程是在该进程内部被调度执行的执行流。没有进程作为"容器"和资源边界,线程无法单独存在,因此说没有进程就没有线程。
为什么多线程执行时,打印顺序可能每次运行都不一样?
查看参考答案 ▼
多个线程谁先获得 CPU、执行到哪一行,由操作系统和解释器的调度策略决定,不是代码书写顺序保证的。因此并发时输出顺序不确定属正常现象。
✓ 正确
参考答案:多个线程
谁先获得 CPU、执行到哪一行,由操作系统和解释器的
调度策略决定,不是代码书写顺序保证的。因此并发时输出顺序
不确定属正常现象。
线程之间共享全局变量与进程之间不共享,区别的原因是什么?
查看参考答案 ▼
同一进程内的线程共用同一地址空间,全局变量在同进程内只有一份;而每个进程有独立的虚拟地址空间,子进程是父进程内存的拷贝,因此各进程的全局变量互不天然共享。
✓ 正确
参考答案:同一进程内的线程共用
同一地址空间,全局变量在同进程内
只有一份;而每个进程有
独立的虚拟地址空间,子进程是父进程内存的拷贝,因此各进程的全局变量互不天然共享。
互斥锁要解决什么问题?使用 with lock: 有什么好处?
查看参考答案 ▼
互斥锁用于线程同步,保证同一时刻只有一个线程进入临界区操作共享数据,避免"读—改—写"被抢占导致的数据竞争。with lock: 在离开代码块时自动释放锁,减少忘记 release() 造成的死锁问题。
✓ 正确
参考答案:互斥锁用于
线程同步,保证
同一时刻只有一个线程进入临界区操作共享数据,避免"读—改—写"被抢占导致的数据竞争。
with lock: 在离开代码块时
自动释放锁,减少忘记 release() 造成的
死锁问题。
简要说明 GIL 与 threading.Lock 的区别;为什么在 CPython 里有时仍要给共享数据加 Lock?
查看参考答案 ▼
GIL 是 CPython 解释器层的全局解释器锁,限制同一时刻通常只有一条线程在执行 Python 字节码。threading.Lock 是程序员为保护自己共享数据创建的显式锁。即使有 GIL,一条源码语句可能对应多条字节码,线程仍可能在中间被切换,因此业务上的非原子更新仍可能出错。
✓ 正确
参考答案:GIL 是 CPython 解释器层的
全局解释器锁,限制同一时刻通常只有一条线程在执行 Python 字节码。
threading.Lock 是程序员为保护自己共享数据创建的
显式锁。即使有 GIL,一条源码语句可能对应
多条字节码,线程仍可能在中间被切换,因此
业务上的非原子更新仍可能出错。
生成器相对一次性构造完整列表,主要优点是什么?yield 与 return 在生成器函数中的典型区别?
查看参考答案 ▼
生成器按需产出元素,不一次性把所有结果放进内存,节省内存。yield 会暂停当前函数、向外产出一个值,下次从暂停处继续;return 用于结束生成器函数(结束迭代)。
✓ 正确
参考答案:生成器
按需产出元素,不一次性把所有结果放进内存,
节省内存。
yield 会
暂停当前函数、向外产出一个值,下次从暂停处继续;
return 用于
结束生成器函数(结束迭代)。
协程三要素是什么?asyncio.run 与 asyncio.create_task 分别适合什么场景?
查看参考答案 ▼
三要素:async def 定义协程函数、await 等待可等待对象、asyncio.run 启动事件循环。asyncio.run 常用于最外层启动整个异步程序;在同一 async def 里要对多个协程并发时,用 create_task 将协程包装为任务。
✓ 正确
参考答案:三要素:
async def 定义协程函数、
await 等待可等待对象、
asyncio.run 启动事件循环。
asyncio.run 常用于
最外层启动整个异步程序;在同一 async def 里要对多个协程
并发时,用
create_task 将协程包装为任务。
四、代码实战(7 题)
多线程入门(必做)
定义两个无参任务函数(如模拟"写代码""听音乐"打印若干行)。
创建两个 threading.Thread,指定 target,调用 start()。
观察多次运行输出顺序是否可能不同。
查看参考答案 ▼
import threading
def coding():
for i in range(3):
print("coding", i)
def music():
for i in range(3):
print("music", i)
t1 = threading.Thread(target=coding)
t2 = threading.Thread(target=music)
t1.start()
t2.start()
✓ 正确
参考答案:import threading
def coding():
for i in range(3):
print("coding", i)
def music():
for i in range(3):
print("music", i)
t1 = threading.Thread(target=coding)
t2 = threading.Thread(target=music)
t1.start()
t2.start()
线程传参(必做)
使用 args=('小明', 3) 按位置传参。
使用 kwargs={...} 按关键字传参。
说明 args 单元素必须加逗号的原因(注释)。
查看参考答案 ▼
def greet(name, times):
for i in range(times):
print(name, i)
def show(**kwargs):
print(kwargs)
t1 = threading.Thread(target=greet, args=("小明", 3))
t2 = threading.Thread(target=show, kwargs={"a": 1, "b": 2})
t1.start()
t2.start()
# 单元素元组: args=(5,) 不能写成 args=(5)
✓ 正确
参考答案:def greet(name, times):
for i in range(times):
print(name, i)
def show(**kwargs):
print(kwargs)
t1 = threading.Thread(target=greet, args=("小明", 3))
t2 = threading.Thread(target=show, kwargs={"a": 1, "b": 2})
t1.start()
t2.start()
# 单元素元组: args=(5,) 不能写成 args=(5)
全局变量与互斥锁(重点必做)
定义全局整型 count = 0,两个线程各循环较大次数(如 10 万次)执行 count += 1。
先不加锁运行,观察结果是否小于预期。
再用同一把 threading.Lock 包裹临界区。
查看参考答案 ▼
import threading
count = 0
lock = threading.Lock()
N = 100000
def add():
global count
for _ in range(N):
with lock:
count += 1
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=add)
t1.start()
t2.start()
t1.join()
t2.join()
print(count) # 200000
✓ 正确
参考答案:import threading
count = 0
lock = threading.Lock()
N = 100000
def add():
global count
for _ in range(N):
with lock:
count += 1
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=add)
t1.start()
t2.start()
t1.join()
t2.join()
print(count) # 200000
生成器(必做)
用生成器推导式创建生成器,并用 for 遍历打印。
再写一个含 yield 的生成器函数,用 next 至少取两次,说明每次从何处继续执行(注释)。
查看参考答案 ▼
g = (x * 2 for x in range(4))
for v in g:
print(v)
def gen(n):
for i in range(n):
yield i
it = gen(3)
print(next(it)) # 0
print(next(it)) # 1
✓ 正确
参考答案:g = (x * 2 for x in range(4))
for v in g:
print(v)
def gen(n):
for i in range(n):
yield i
it = gen(3)
print(next(it)) # 0
print(next(it)) # 1
asyncio 入门与并发任务(重点必做)
定义 async def work(name): 内 print 开始,await asyncio.sleep(1),print 结束。
定义 async def main(),用 create_task 同时调度至少两个 work,再 await 等待。
最外层用 asyncio.run(main())。注释说明并发 vs 串行的耗时区别。
查看参考答案 ▼
import asyncio
async def work(name):
print("start", name)
await asyncio.sleep(1)
print("end", name)
async def main():
t1 = asyncio.create_task(work("A"))
t2 = asyncio.create_task(work("B"))
await t1
await t2
asyncio.run(main())
✓ 正确
参考答案:import asyncio
async def work(name):
print("start", name)
await asyncio.sleep(1)
print("end", name)
async def main():
t1 = asyncio.create_task(work("A"))
t2 = asyncio.create_task(work("B"))
await t1
await t2
asyncio.run(main())
CPU 密集型 vs I/O 密集型(选做)
用一段话对比:CPU 密集型任务在 CPython 下更适合多进程还是多线程?I/O 密集型更适合线程还是协程?简要说明理由。
查看参考答案 ▼
CPU 密集型:在 CPython 下多线程受 GIL 影响,纯 Python 计算往往难以吃满多核,更适合用多进程将任务分配到多核。I/O 密集型:线程可在 I/O 阻塞时让出 CPU;协程在单线程事件循环里通过 await 挂起等待,大量 I/O 并发时开销更小,适合高并发网络/等待场景。
✓ 正确
参考答案:CPU 密集型:在 CPython 下多线程受 GIL 影响,纯 Python 计算往往难以吃满多核,更适合用多进程将任务分配到多核。I/O 密集型:线程可在 I/O 阻塞时让出 CPU;协程在单线程事件循环里通过 await 挂起等待,大量 I/O 并发时开销更小,适合高并发网络/等待场景。
join() vs 互斥锁(选做)
查阅或回忆:join() 等待子线程结束与用互斥锁保护临界区,在"牺牲并行度"方面有何异同(各写一两句)。
查看参考答案 ▼
join():让调用方等待某个线程先跑完,其他线程期间是否并行取决于你 join 的方式,容易写成阶段性串行。互斥锁:只保证临界区串行,锁外其他线程仍可并行,并行度通常好于整条线程顺序 join 的写法,但锁粒度过大也会接近单线程。
✓ 正确
参考答案:join():让调用方等待某个线程先跑完,其他线程期间是否并行取决于你 join 的方式,容易写成阶段性串行。互斥锁:只保证临界区串行,锁外其他线程仍可并行,并行度通常好于整条线程顺序 join 的写法,但锁粒度过大也会接近单线程。